From ef33df5e22ab8a3cf867628a3cad5bcc5e892da7 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 24 Sep 2026 08:53:49 +0000 Subject: [PATCH] refactor(api): tighten app detail response schema (#42876) --- api/controllers/console/agent/roster.py | 7 +- api/controllers/console/app/app.py | 12 +- api/controllers/console/app/workflow.py | 4 +- .../manager.py | 26 +- .../features/text_to_speech/manager.py | 15 + api/fields/app_fields.py | 168 +-- api/fields/app_model_config_response.py | 388 +++++++ api/models/agent_config_entities.py | 2 +- api/models/model.py | 24 +- api/openapi/markdown/console-openapi.md | 647 +++++++++-- api/openapi/markdown/openapi-openapi.md | 4 +- api/services/app/console_service.py | 5 +- api/services/app_dsl_service.py | 6 + api/services/app_service.py | 76 +- api/services/audio_service.py | 3 +- api/services/entities/dsl_entities.py | 2 +- .../app/test_app_detail_config_integrity.py | 86 ++ .../services/test_app_service.py | 49 - .../commands/test_generate_swagger_specs.py | 8 + .../console/agent/test_agent_controllers.py | 15 +- .../console/app/test_app_response_models.py | 178 ++- .../console/app/test_console_app_api.py | 32 +- .../explore/test_trial_preview_details.py | 5 +- .../test_additional_feature_managers.py | 14 + .../unit_tests/models/test_app_models.py | 12 + .../test_console_app_repository.py | 20 +- .../services/app/test_console_service.py | 5 +- .../services/test_app_dsl_service.py | 46 + .../unit_tests/services/test_app_service.py | 88 +- .../generated/api/console/agent/types.gen.ts | 657 ++++++++--- .../generated/api/console/agent/zod.gen.ts | 994 ++++++++++++---- .../generated/api/console/apps/types.gen.ts | 711 +++++++++--- .../generated/api/console/apps/zod.gen.ts | 1011 +++++++++++++---- .../api/console/snippets/types.gen.ts | 2 +- .../generated/api/console/snippets/zod.gen.ts | 2 +- .../generated/api/openapi/types.gen.ts | 4 +- .../generated/api/openapi/zod.gen.ts | 4 +- .../__tests__/use-app-info-actions.spec.ts | 6 +- .../app-info/use-app-info-actions.ts | 5 +- .../settings-modal/__tests__/index.spec.tsx | 9 +- .../__tests__/use-configuration.spec.tsx | 20 +- .../switch-app-modal/__tests__/index.spec.tsx | 2 +- .../app-selector/app-inputs-panel.tsx | 4 +- .../hooks/use-app-inputs-form-schema.ts | 3 +- .../__tests__/navigation.spec.tsx | 34 +- .../__tests__/access-surface-cards.spec.tsx | 98 +- .../access/components/web-app-access-card.tsx | 33 +- .../__tests__/publish-bar.browser.spec.tsx | 6 +- .../__tests__/publish-bar.spec.tsx | 6 +- web/service/console/client.spec.ts | 35 +- web/test/fixtures/agent.ts | 2 + web/test/fixtures/app.ts | 73 ++ 52 files changed, 4295 insertions(+), 1373 deletions(-) create mode 100644 api/fields/app_model_config_response.py create mode 100644 api/tests/test_containers_integration_tests/controllers/console/app/test_app_detail_config_integrity.py create mode 100644 web/test/fixtures/app.ts diff --git a/api/controllers/console/agent/roster.py b/api/controllers/console/agent/roster.py index 576b9be7987..348abb7568e 100644 --- a/api/controllers/console/agent/roster.py +++ b/api/controllers/console/agent/roster.py @@ -274,6 +274,7 @@ class AgentAppPartial(GenericAppPartial): class AgentAppDetailWithSite(GenericAppDetailWithSite): permission_keys: list[str] + bound_agent_id: str | None = None app_id: str | None = None backing_app_id: str | None = None hidden_app_backed: bool = False @@ -404,14 +405,14 @@ def _serialize_agent_app_detail( roster persona fields without widening the shared /apps detail schema. """ - app_model = AppService().get_app(app_model, session=session) + access_mode = None if SystemFeatureService.is_webapp_auth_enabled(): app_setting = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=str(app_model.id)) - app_model.access_mode = app_setting.access_mode # type: ignore[attr-defined] + access_mode = app_setting.access_mode roster_service = _agent_roster_service(session) payload = GenericAppDetailWithSite.model_validate( - AppResponseView(app_model, session=session), + AppResponseView(app_model, session=session, account=current_user, access_mode=access_mode), from_attributes=True, ).model_dump(mode="json") agent = ( diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 9d624b71ab0..58019cbdda1 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -1,4 +1,5 @@ import uuid +from dataclasses import asdict from http import HTTPStatus from typing import Literal, Self, override @@ -303,11 +304,12 @@ _ADMIN_ROLES = frozenset({TenantAccountRole.OWNER, TenantAccountRole.ADMIN}) def _app_detail_response(app: AppRecord) -> AppDetailWithSite: """Resolve transport URLs here so repositories work without a Flask request.""" - response = AppDetailWithSite.model_validate(app, from_attributes=True) - response.api_base_url = normalize_api_base_url(dify_config.SERVICE_API_URL or request.host_url.rstrip("/")) - if response.site is not None: - response.site.app_base_url = dify_config.APP_WEB_URL or request.url_root.rstrip("/") - return response + data = asdict(app) + data["api_base_url"] = normalize_api_base_url(dify_config.SERVICE_API_URL or request.host_url.rstrip("/")) + data["app_id"] = None + if data["site"] is not None: + data["site"]["app_base_url"] = dify_config.APP_WEB_URL or request.url_root.rstrip("/") + return AppDetailWithSite.model_validate(data) class AppResource(Resource): diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index c646589f126..9a2f85ff90a 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -2,7 +2,7 @@ import json import logging from collections.abc import Sequence from datetime import datetime -from typing import Any, NotRequired, Self, TypedDict +from typing import Any, Literal, NotRequired, Self, TypedDict from flask import abort, request from flask_restx import Resource @@ -216,7 +216,7 @@ class WorkflowSuggestedQuestionsAfterAnswerPayload(WorkflowFeatureTogglePayload) class WorkflowTextToSpeechPayload(WorkflowFeatureTogglePayload): language: str | None = None voice: str | None = None - autoPlay: str | None = None + autoPlay: Literal["enabled", "disabled"] | None = None class WorkflowSensitiveWordAvoidancePayload(WorkflowFeatureTogglePayload): diff --git a/api/core/app/app_config/features/suggested_questions_after_answer/manager.py b/api/core/app/app_config/features/suggested_questions_after_answer/manager.py index 0c36992c777..6163b9345e9 100644 --- a/api/core/app/app_config/features/suggested_questions_after_answer/manager.py +++ b/api/core/app/app_config/features/suggested_questions_after_answer/manager.py @@ -4,6 +4,23 @@ CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH = 1000 class SuggestedQuestionsAfterAnswerConfigManager: + @staticmethod + def validate_optional_fields(config: dict[str, Any]) -> None: + feature = config.get("suggested_questions_after_answer") + if feature is None: + return + if not isinstance(feature, dict): + raise ValueError("suggested_questions_after_answer must be of dict type") + if "prompt" in feature: + prompt = feature["prompt"] + if not isinstance(prompt, str): + raise ValueError("prompt in suggested_questions_after_answer must be of string type") + if len(prompt) > CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH: + raise ValueError( + f"prompt in suggested_questions_after_answer must be less than or equal to " + f"{CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH} characters" + ) + @classmethod def convert(cls, config: dict[str, Any]) -> bool: """ @@ -45,14 +62,7 @@ class SuggestedQuestionsAfterAnswerConfigManager: if not isinstance(config["suggested_questions_after_answer"]["enabled"], bool): raise ValueError("enabled in suggested_questions_after_answer must be of boolean type") - prompt = config["suggested_questions_after_answer"].get("prompt") - if prompt is not None and not isinstance(prompt, str): - raise ValueError("prompt in suggested_questions_after_answer must be of string type") - if isinstance(prompt, str) and len(prompt) > CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH: - raise ValueError( - f"prompt in suggested_questions_after_answer must be less than or equal to " - f"{CUSTOM_FOLLOW_UP_PROMPT_MAX_LENGTH} characters" - ) + cls.validate_optional_fields(config) if "model" in config["suggested_questions_after_answer"]: model_config = config["suggested_questions_after_answer"]["model"] diff --git a/api/core/app/app_config/features/text_to_speech/manager.py b/api/core/app/app_config/features/text_to_speech/manager.py index ca84ec9c3b5..810c0a80c2e 100644 --- a/api/core/app/app_config/features/text_to_speech/manager.py +++ b/api/core/app/app_config/features/text_to_speech/manager.py @@ -4,6 +4,19 @@ from core.app.app_config.entities import TextToSpeechEntity class TextToSpeechConfigManager: + @staticmethod + def validate_optional_fields(config: dict[str, Any]) -> None: + feature = config.get("text_to_speech") + if feature is None: + return + if not isinstance(feature, dict): + raise ValueError("text_to_speech must be of dict type") + for key in ("voice", "language"): + if key in feature and not isinstance(feature[key], str): + raise ValueError(f"{key} in text_to_speech must be of string type") + if "autoPlay" in feature and feature["autoPlay"] not in ("enabled", "disabled"): + raise ValueError("autoPlay in text_to_speech must be enabled or disabled") + @classmethod def convert(cls, config: dict[str, Any]): """ @@ -36,6 +49,8 @@ class TextToSpeechConfigManager: if not isinstance(config["text_to_speech"], dict): raise ValueError("text_to_speech must be of dict type") + cls.validate_optional_fields(config) + if "enabled" not in config["text_to_speech"] or not config["text_to_speech"]["enabled"]: config["text_to_speech"]["enabled"] = False config["text_to_speech"]["voice"] = "" diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index 9a446971274..92408bac755 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -1,13 +1,15 @@ """Shared Console app response schemas for materialized application results.""" from datetime import datetime -from typing import Any from pydantic import AliasChoices, Field, computed_field, field_validator +from enums import WebAppAccessMode +from fields.app_model_config_response import AppModelConfigResponse, AppModelSelectionResponse from fields.base import ResponseModel from libs.helper import build_icon_url, to_timestamp -from models.model import IconType +from models.enums import CustomizeTokenStrategy +from models.model import AppMode, IconType from services.entities.app_entities import RecentAppMode from services.entities.dsl_entities import DslImportWarning, ImportStatus @@ -37,7 +39,7 @@ class WorkflowPartial(ResponseModel): class ModelConfigPartial(ResponseModel): - model: Any | None = Field(default=None, validation_alias=AliasChoices("model_dict", "model")) + model: AppModelSelectionResponse | None = Field(default=None, validation_alias=AliasChoices("model_dict", "model")) pre_prompt: str | None = None created_by: str | None = None created_at: int | None = None @@ -50,90 +52,31 @@ class ModelConfigPartial(ResponseModel): return to_timestamp(value) -class AppModelConfigResponse(ResponseModel): - opening_statement: str | None = None - suggested_questions: Any | None = Field( - default=None, validation_alias=AliasChoices("suggested_questions_list", "suggested_questions") - ) - suggested_questions_after_answer: Any | None = Field( - default=None, - validation_alias=AliasChoices("suggested_questions_after_answer_dict", "suggested_questions_after_answer"), - ) - speech_to_text: Any | None = Field( - default=None, validation_alias=AliasChoices("speech_to_text_dict", "speech_to_text") - ) - text_to_speech: Any | None = Field( - default=None, validation_alias=AliasChoices("text_to_speech_dict", "text_to_speech") - ) - retriever_resource: Any | None = Field( - default=None, validation_alias=AliasChoices("retriever_resource_dict", "retriever_resource") - ) - annotation_reply: Any | None = Field( - default=None, validation_alias=AliasChoices("annotation_reply_dict", "annotation_reply") - ) - more_like_this: Any | None = Field( - default=None, validation_alias=AliasChoices("more_like_this_dict", "more_like_this") - ) - sensitive_word_avoidance: Any | None = Field( - default=None, validation_alias=AliasChoices("sensitive_word_avoidance_dict", "sensitive_word_avoidance") - ) - external_data_tools: Any | None = Field( - default=None, validation_alias=AliasChoices("external_data_tools_list", "external_data_tools") - ) - model: Any | None = Field(default=None, validation_alias=AliasChoices("model_dict", "model")) - user_input_form: Any | None = Field( - default=None, validation_alias=AliasChoices("user_input_form_list", "user_input_form") - ) - dataset_query_variable: str | None = None - pre_prompt: str | None = None - agent_mode: Any | None = Field(default=None, validation_alias=AliasChoices("agent_mode_dict", "agent_mode")) - prompt_type: str | None = None - chat_prompt_config: Any | None = Field( - default=None, validation_alias=AliasChoices("chat_prompt_config_dict", "chat_prompt_config") - ) - completion_prompt_config: Any | None = Field( - default=None, validation_alias=AliasChoices("completion_prompt_config_dict", "completion_prompt_config") - ) - dataset_configs: Any | None = Field( - default=None, validation_alias=AliasChoices("dataset_configs_dict", "dataset_configs") - ) - file_upload: Any | None = Field(default=None, validation_alias=AliasChoices("file_upload_dict", "file_upload")) - created_by: str | None = None - created_at: int | None = None - updated_by: str | None = None - updated_at: int | None = None - - @field_validator("created_at", "updated_at", mode="before") - @classmethod - def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: - return to_timestamp(value) - - class AppDetailSiteResponse(ResponseModel): - access_token: str | None = Field(default=None, validation_alias="code") - code: str | None = None - title: str | None = None - icon_type: str | IconType | None = None - icon: str | None = None - icon_background: str | None = None - description: str | None = None - default_language: str | None = None - chat_color_theme: str | None = None - chat_color_theme_inverted: bool | None = None - customize_domain: str | None = None - copyright: str | None = None - privacy_policy: str | None = None - input_placeholder: str | None = None - custom_disclaimer: str | None = None - customize_token_strategy: str | None = None - prompt_public: bool | None = None - app_base_url: str | None = None - show_workflow_steps: bool | None = None - use_icon_as_answer_icon: bool | None = None - created_by: str | None = None - created_at: int | None = None - updated_by: str | None = None - updated_at: int | None = None + access_token: str | None = Field(validation_alias="code") + code: str | None + title: str + icon_type: IconType | None + icon: str | None + icon_background: str | None + description: str | None + default_language: str + chat_color_theme: str | None + chat_color_theme_inverted: bool + customize_domain: str | None + copyright: str | None + privacy_policy: str | None + input_placeholder: str | None + custom_disclaimer: str + customize_token_strategy: CustomizeTokenStrategy + prompt_public: bool + app_base_url: str + show_workflow_steps: bool + use_icon_as_answer_icon: bool + created_by: str | None + created_at: int + updated_by: str | None + updated_at: int @computed_field(return_type=str | None) # type: ignore @property @@ -164,8 +107,8 @@ class AppPartial(ResponseModel): name: str max_active_requests: int | None = None description: str | None = Field(default=None, validation_alias=AliasChoices("desc_or_prompt", "description")) - mode: str = Field(validation_alias="mode_compatible_with_agent") - icon_type: str | None = None + mode: AppMode = Field(validation_alias="mode_compatible_with_agent") + icon_type: IconType | None = None icon: str | None = None icon_background: str | None = None model_config_: ModelConfigPartial | None = Field( @@ -180,7 +123,7 @@ class AppPartial(ResponseModel): updated_by: str | None = None updated_at: int | None = None tags: list[Tag] = Field(default_factory=list) - access_mode: str | None = None + access_mode: WebAppAccessMode | None = None create_user_name: str | None = None author_name: str | None = None has_draft_trigger: bool | None = None @@ -233,28 +176,27 @@ class RecentAppListResponse(ResponseModel): class AppDetail(ResponseModel): id: str name: str - description: str | None = None - mode: str = Field(validation_alias="mode_compatible_with_agent") - icon: str | None = None - icon_background: str | None = None + description: str + mode: AppMode = Field(validation_alias="mode_compatible_with_agent") + icon: str | None + icon_background: str | None enable_site: bool enable_api: bool model_config_: AppModelConfigResponse | None = Field( - default=None, validation_alias=AliasChoices("app_model_config", "model_config"), alias="model_config", ) - workflow: WorkflowPartial | None = None - tracing: Any | None = None - use_icon_as_answer_icon: bool | None = None - created_by: str | None = None - created_at: int | None = None - updated_by: str | None = None - updated_at: int | None = None - access_mode: str | None = None - tags: list[Tag] = Field(default_factory=list) - permission_keys: list[str] = Field(default_factory=list) - maintainer: str | None = None + workflow: WorkflowPartial | None + tracing: str | None + use_icon_as_answer_icon: bool + created_by: str | None + created_at: int + updated_by: str | None + updated_at: int + access_mode: WebAppAccessMode | None + tags: list[Tag] + permission_keys: list[str] + maintainer: str | None @field_validator("created_at", "updated_at", mode="before") @classmethod @@ -263,15 +205,15 @@ class AppDetail(ResponseModel): class AppDetailWithSite(AppDetail): - icon_type: str | None = None - api_base_url: str | None = None - max_active_requests: int | None = None - deleted_tools: list[DeletedTool] = Field(default_factory=list) - site: AppDetailSiteResponse | None = None + icon_type: IconType | None + api_base_url: str + max_active_requests: int | None + deleted_tools: list[DeletedTool] + site: AppDetailSiteResponse | None # For Agent App type: the roster Agent backing this app (None otherwise). - bound_agent_id: str | None = None + bound_agent_id: str | None # For Agent App responses exposed through /agent. - app_id: str | None = None + app_id: str | None @computed_field(return_type=str | None) # type: ignore @property @@ -295,7 +237,7 @@ class AppImportResponse(ResponseModel): id: str status: ImportStatus app_id: str | None = None - app_mode: str | None = None + app_mode: AppMode | None = None current_dsl_version: str imported_dsl_version: str = "" error: str = "" diff --git a/api/fields/app_model_config_response.py b/api/fields/app_model_config_response.py new file mode 100644 index 00000000000..f8bf05a9dd6 --- /dev/null +++ b/api/fields/app_model_config_response.py @@ -0,0 +1,388 @@ +from datetime import datetime +from typing import Literal, NotRequired, TypedDict + +from pydantic import AliasChoices, ConfigDict, Field, JsonValue, field_validator, with_config + +from core.entities.agent_entities import PlanningStrategy +from core.rag.entities.metadata_entities import SupportedComparisonOperator +from core.rag.rerank.rerank_type import RerankMode +from core.tools.entities.tool_entities import ToolProviderType +from fields.base import ResponseModel +from graphon.file import FileTransferMethod, FileType +from graphon.model_runtime.entities.llm_entities import LLMMode +from libs.helper import to_timestamp +from models.enums import PromptType + + +class AppEnabledConfigResponse(TypedDict): + enabled: bool + + +@with_config(ConfigDict(extra="allow")) +class AppModelSelectionResponse(TypedDict, total=False): + provider: str + name: str + mode: LLMMode | Literal[""] + completion_params: dict[str, JsonValue] + + +class AppSuggestedQuestionsAfterAnswerResponse(TypedDict): + enabled: bool + model: NotRequired[AppModelSelectionResponse] + prompt: NotRequired[str] + + +class AppTextToSpeechResponse(TypedDict): + enabled: bool + voice: NotRequired[str] + language: NotRequired[str] + autoPlay: NotRequired[Literal["enabled", "disabled"]] + + +class AppEmbeddingModelResponse(TypedDict): + embedding_provider_name: str + embedding_model_name: str + + +class AppAnnotationReplyDisabledResponse(TypedDict): + enabled: Literal[False] + + +class AppAnnotationReplyEnabledResponse(TypedDict): + id: str + enabled: Literal[True] + score_threshold: float + embedding_model: AppEmbeddingModelResponse + + +@with_config(ConfigDict(extra="allow")) +class AppSensitiveWordAvoidanceResponse(TypedDict): + enabled: bool + type: NotRequired[str] + config: NotRequired[dict[str, JsonValue]] + configs: NotRequired[list[JsonValue]] + + +@with_config(ConfigDict(extra="allow")) +class AppEnabledExternalDataToolResponse(TypedDict): + enabled: Literal[True] + variable: str + type: str + config: dict[str, JsonValue] + label: NotRequired[str] + icon: NotRequired[str] + icon_background: NotRequired[str] + + +@with_config(ConfigDict(extra="allow")) +class AppDisabledExternalDataToolResponse(TypedDict): + enabled: Literal[False] + variable: NotRequired[str] + type: NotRequired[str] + config: NotRequired[dict[str, JsonValue]] + label: NotRequired[str] + icon: NotRequired[str] + icon_background: NotRequired[str] + + +@with_config(ConfigDict(extra="allow")) +class AppUserInputFormConfigResponse(TypedDict): + variable: str + label: str + description: NotRequired[str] + required: NotRequired[bool] + max_length: NotRequired[int | None] + options: NotRequired[list[str]] + default: NotRequired[JsonValue] + type: NotRequired[str] + hide: NotRequired[bool] + allowed_file_types: NotRequired[list[FileType]] + allowed_file_extensions: NotRequired[list[str]] + allowed_file_upload_methods: NotRequired[list[FileTransferMethod]] + json_schema: NotRequired[str | dict[str, JsonValue] | None] + config: NotRequired[dict[str, JsonValue]] + enabled: NotRequired[bool] + icon: NotRequired[str | None] + icon_background: NotRequired[str | None] + + +AppTextInputFormResponse = TypedDict("AppTextInputFormResponse", {"text-input": AppUserInputFormConfigResponse}) + + +class AppSelectFormResponse(TypedDict): + select: AppUserInputFormConfigResponse + + +class AppParagraphFormResponse(TypedDict): + paragraph: AppUserInputFormConfigResponse + + +class AppNumberFormResponse(TypedDict): + number: AppUserInputFormConfigResponse + + +class AppCheckboxFormResponse(TypedDict): + checkbox: AppUserInputFormConfigResponse + + +class AppFileFormResponse(TypedDict): + file: AppUserInputFormConfigResponse + + +AppFileListFormResponse = TypedDict("AppFileListFormResponse", {"file-list": AppUserInputFormConfigResponse}) + + +class AppExternalDataToolFormResponse(TypedDict): + external_data_tool: AppUserInputFormConfigResponse + + +class AppJsonObjectFormResponse(TypedDict): + json_object: AppUserInputFormConfigResponse + + +AppUserInputFormResponse = ( + AppTextInputFormResponse + | AppSelectFormResponse + | AppParagraphFormResponse + | AppNumberFormResponse + | AppCheckboxFormResponse + | AppFileFormResponse + | AppFileListFormResponse + | AppExternalDataToolFormResponse + | AppJsonObjectFormResponse +) + + +class AppDatasetReferenceResponse(TypedDict, total=False): + id: str + enabled: bool + + +class AppLegacySensitiveWordToolResponse(TypedDict): + enabled: bool + words: list[str] + canned_response: str + + +@with_config(ConfigDict(extra="allow")) +class AppProviderAgentToolResponse(TypedDict): + provider_type: ToolProviderType + provider_id: str + tool_name: str + tool_parameters: dict[str, JsonValue] + provider_name: NotRequired[str] + tool_label: NotRequired[str] + plugin_unique_identifier: NotRequired[str | None] + credential_id: NotRequired[str | None] + enabled: NotRequired[bool] + isDeleted: NotRequired[bool] + notAuthor: NotRequired[bool] + + +class AppLegacyDatasetToolResponse(TypedDict): + dataset: AppDatasetReferenceResponse + + +class AppLegacyGoogleSearchToolResponse(TypedDict): + google_search: dict[str, JsonValue] + + +class AppLegacyWebReaderToolResponse(TypedDict): + web_reader: dict[str, JsonValue] + + +class AppLegacyWikipediaToolResponse(TypedDict): + wikipedia: dict[str, JsonValue] + + +class AppLegacyCurrentDatetimeToolResponse(TypedDict): + current_datetime: dict[str, JsonValue] + + +AppLegacySensitiveWordToolResponseItem = TypedDict( + "AppLegacySensitiveWordToolResponseItem", + {"sensitive-word-avoidance": AppLegacySensitiveWordToolResponse}, +) + + +AppAgentToolResponse = ( + AppProviderAgentToolResponse + | AppLegacyDatasetToolResponse + | AppLegacyGoogleSearchToolResponse + | AppLegacyWebReaderToolResponse + | AppLegacyWikipediaToolResponse + | AppLegacyCurrentDatetimeToolResponse + | AppLegacySensitiveWordToolResponseItem +) + + +class AppAgentPromptResponse(TypedDict, total=False): + first_prompt: str + next_iteration: str + + +@with_config(ConfigDict(extra="allow")) +class AppAgentModeResponse(TypedDict): + enabled: bool + strategy: NotRequired[PlanningStrategy | Literal["cot", "function-calling"] | None] + tools: NotRequired[list[AppAgentToolResponse]] + prompt: NotRequired[AppAgentPromptResponse | str | None] + max_iteration: NotRequired[int] + + +class AppChatPromptMessageResponse(TypedDict): + text: str + role: str + + +class AppChatPromptConfigResponse(TypedDict, total=False): + prompt: list[AppChatPromptMessageResponse] + + +class AppCompletionPromptTextResponse(TypedDict): + text: str + + +class AppConversationHistoriesRoleResponse(TypedDict): + user_prefix: str + assistant_prefix: str + + +class AppCompletionPromptConfigResponse(TypedDict, total=False): + prompt: AppCompletionPromptTextResponse + conversation_histories_role: AppConversationHistoriesRoleResponse + + +class AppDatasetItemResponse(TypedDict): + dataset: AppDatasetReferenceResponse + + +class AppDatasetListResponse(TypedDict): + datasets: list[AppDatasetItemResponse] + strategy: NotRequired[str] + + +class AppRerankingModelResponse(TypedDict, total=False): + reranking_provider_name: str + reranking_model_name: str + + +class AppVectorSettingResponse(TypedDict): + vector_weight: float + embedding_provider_name: str + embedding_model_name: str + + +class AppKeywordSettingResponse(TypedDict): + keyword_weight: float + + +class AppWeightsResponse(TypedDict): + vector_setting: AppVectorSettingResponse + keyword_setting: AppKeywordSettingResponse + weight_type: NotRequired[Literal["semantic_first", "keyword_first", "customized"]] + + +@with_config(ConfigDict(extra="allow")) +class AppMetadataConditionResponse(TypedDict): + id: NotRequired[str] + metadata_id: NotRequired[str] + name: str + comparison_operator: SupportedComparisonOperator + value: NotRequired[str | list[str] | int | float | None] + + +class AppMetadataFilteringConditionsResponse(TypedDict, total=False): + logical_operator: Literal["and", "or"] | None + conditions: list[AppMetadataConditionResponse] | None + + +@with_config(ConfigDict(extra="allow")) +class AppDatasetConfigsResponse(TypedDict): + retrieval_model: Literal["single", "multiple"] + datasets: NotRequired[AppDatasetListResponse] + top_k: NotRequired[int] + score_threshold: NotRequired[float | None] + score_threshold_enabled: NotRequired[bool] + reranking_model: NotRequired[AppRerankingModelResponse | None] + weights: NotRequired[AppWeightsResponse | None] + reranking_enabled: NotRequired[bool] + reranking_enable: NotRequired[bool] + reranking_mode: NotRequired[RerankMode] + metadata_filtering_mode: NotRequired[Literal["disabled", "automatic", "manual"]] + metadata_model_config: NotRequired[AppModelSelectionResponse | None] + metadata_filtering_conditions: NotRequired[AppMetadataFilteringConditionsResponse | None] + + +class AppImageUploadResponse(TypedDict, total=False): + enabled: bool + number_limits: int + detail: Literal["low", "high"] | None + transfer_methods: list[FileTransferMethod] + + +class AppFileUploadResponse(TypedDict, total=False): + image: AppImageUploadResponse + enabled: bool + allowed_file_types: list[FileType] + allowed_file_extensions: list[str] + allowed_file_upload_methods: list[FileTransferMethod] + number_limits: int + + +class AppModelConfigResponse(ResponseModel): + opening_statement: str | None + suggested_questions: list[str] = Field( + validation_alias=AliasChoices("suggested_questions_list", "suggested_questions") + ) + suggested_questions_after_answer: AppSuggestedQuestionsAfterAnswerResponse = Field( + validation_alias=AliasChoices("suggested_questions_after_answer_dict", "suggested_questions_after_answer"), + ) + speech_to_text: AppEnabledConfigResponse = Field( + validation_alias=AliasChoices("speech_to_text_dict", "speech_to_text") + ) + text_to_speech: AppTextToSpeechResponse = Field( + validation_alias=AliasChoices("text_to_speech_dict", "text_to_speech") + ) + retriever_resource: AppEnabledConfigResponse = Field( + validation_alias=AliasChoices("retriever_resource_dict", "retriever_resource") + ) + annotation_reply: AppAnnotationReplyEnabledResponse | AppAnnotationReplyDisabledResponse = Field( + validation_alias=AliasChoices("annotation_reply_dict", "annotation_reply") + ) + more_like_this: AppEnabledConfigResponse = Field( + validation_alias=AliasChoices("more_like_this_dict", "more_like_this") + ) + sensitive_word_avoidance: AppSensitiveWordAvoidanceResponse = Field( + validation_alias=AliasChoices("sensitive_word_avoidance_dict", "sensitive_word_avoidance") + ) + external_data_tools: list[AppEnabledExternalDataToolResponse | AppDisabledExternalDataToolResponse] = Field( + validation_alias=AliasChoices("external_data_tools_list", "external_data_tools") + ) + model: AppModelSelectionResponse = Field(validation_alias=AliasChoices("model_dict", "model")) + user_input_form: list[AppUserInputFormResponse] = Field( + validation_alias=AliasChoices("user_input_form_list", "user_input_form") + ) + dataset_query_variable: str | None + pre_prompt: str | None + agent_mode: AppAgentModeResponse = Field(validation_alias=AliasChoices("agent_mode_dict", "agent_mode")) + prompt_type: PromptType + chat_prompt_config: AppChatPromptConfigResponse = Field( + validation_alias=AliasChoices("chat_prompt_config_dict", "chat_prompt_config") + ) + completion_prompt_config: AppCompletionPromptConfigResponse = Field( + validation_alias=AliasChoices("completion_prompt_config_dict", "completion_prompt_config") + ) + dataset_configs: AppDatasetConfigsResponse = Field( + validation_alias=AliasChoices("dataset_configs_dict", "dataset_configs") + ) + file_upload: AppFileUploadResponse = Field(validation_alias=AliasChoices("file_upload_dict", "file_upload")) + created_by: str | None + created_at: int + updated_by: str | None + updated_at: int + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: + return to_timestamp(value) diff --git a/api/models/agent_config_entities.py b/api/models/agent_config_entities.py index 02479a3147b..d3900fa3e77 100644 --- a/api/models/agent_config_entities.py +++ b/api/models/agent_config_entities.py @@ -546,7 +546,7 @@ class AgentFeatureToggleConfig(AgentFlexibleConfig): class AgentTextToSpeechFeatureConfig(AgentFeatureToggleConfig): language: str | None = None voice: str | None = None - autoPlay: str | None = None + autoPlay: Literal["enabled", "disabled"] | None = None class AgentSuggestedQuestionsAfterAnswerModelConfig(AgentFlexibleConfig): diff --git a/api/models/model.py b/api/models/model.py index 88785ee943e..6148ecab845 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -99,6 +99,13 @@ class EnabledConfig(TypedDict): enabled: bool +class TextToSpeechConfig(TypedDict): + enabled: bool + voice: NotRequired[str] + language: NotRequired[str] + autoPlay: NotRequired[Literal["enabled", "disabled"]] + + class SuggestedQuestionsAfterAnswerModelConfig(TypedDict): provider: str name: str @@ -148,7 +155,7 @@ class AgentToolConfig(TypedDict): class AgentModeConfig(TypedDict): enabled: bool - strategy: str | None + strategy: NotRequired[str | None] tools: list[AgentToolConfig | dict[str, Any]] prompt: str | None @@ -175,6 +182,9 @@ class ExternalDataToolConfig(TypedDict): variable: str type: str config: dict[str, Any] + label: NotRequired[str] + icon: NotRequired[str] + icon_background: NotRequired[str] class UserInputFormItemConfig(TypedDict): @@ -187,6 +197,9 @@ class UserInputFormItemConfig(TypedDict): default: NotRequired[str] type: NotRequired[str] config: NotRequired[dict[str, Any]] + enabled: NotRequired[bool] + icon: NotRequired[str] + icon_background: NotRequired[str] # Each item is a single-key dict, e.g. {"text-input": UserInputFormItemConfig} @@ -202,6 +215,7 @@ class DatasetConfigs(TypedDict): reranking_model: NotRequired[dict[str, Any] | None] weights: NotRequired[dict[str, Any] | None] reranking_enabled: NotRequired[bool] + reranking_enable: NotRequired[bool] reranking_mode: NotRequired[str] metadata_filtering_mode: NotRequired[str] metadata_model_config: NotRequired[dict[str, Any] | None] @@ -243,7 +257,7 @@ class AppModelConfigDict(TypedDict): suggested_questions: list[str] suggested_questions_after_answer: SuggestedQuestionsAfterAnswerConfig speech_to_text: EnabledConfig - text_to_speech: EnabledConfig + text_to_speech: TextToSpeechConfig retriever_resource: EnabledConfig annotation_reply: AnnotationReplyConfig more_like_this: EnabledConfig @@ -786,8 +800,8 @@ class AppModelConfig(TypeBase): return self._get_enabled_config(self.speech_to_text) @property - def text_to_speech_dict(self) -> EnabledConfig: - return self._get_enabled_config(self.text_to_speech) + def text_to_speech_dict(self) -> TextToSpeechConfig: + return cast(TextToSpeechConfig, self._get_enabled_config(self.text_to_speech)) @property def retriever_resource_dict(self) -> EnabledConfig: @@ -843,7 +857,7 @@ class AppModelConfig(TypeBase): if self.dataset_configs: dataset_configs = json.loads(self.dataset_configs) if "retrieval_model" not in dataset_configs: - return {"retrieval_model": "single"} + return {**dataset_configs, "retrieval_model": "single"} else: return cast(DatasetConfigs, dataset_configs) return { diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 5442befdf80..a082df42a83 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -13670,41 +13670,41 @@ Model class for AI model. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| access_mode | string | | No | +| access_mode | [WebAppAccessMode](#webappaccessmode) | | Yes | | access_ready | boolean | | No | -| api_base_url | string | | No | +| api_base_url | string | | Yes | | app_id | string | | No | | backing_app_id | string | | No | | bound_agent_id | string | | No | -| created_at | integer | | No | -| created_by | string | | No | +| created_at | integer | | Yes | +| created_by | string | | Yes | | debug_conversation_has_messages | boolean | | No | | debug_conversation_id | string | | No | | debug_conversation_message_count | integer | | No | -| deleted_tools | [ [DeletedTool](#deletedtool) ] | | No | -| description | string | | No | +| deleted_tools | [ [DeletedTool](#deletedtool) ] | | Yes | +| description | string | | Yes | | enable_api | boolean | | Yes | | enable_site | boolean | | Yes | | hidden_app_backed | boolean | | No | -| icon | string | | No | -| icon_background | string | | No | -| icon_type | string | | No | +| icon | string | | Yes | +| icon_background | string | | Yes | +| icon_type | [IconType](#icontype) | | Yes | | icon_url | string | | Yes | | id | string | | Yes | -| maintainer | string | | No | -| max_active_requests | integer | | No | -| mode | string | | Yes | -| model_config | [AppModelConfigResponse](#appmodelconfigresponse) | | No | +| maintainer | string | | Yes | +| max_active_requests | integer | | Yes | +| mode | [AppMode](#appmode) | | Yes | +| model_config | [AppModelConfigResponse](#appmodelconfigresponse) | | Yes | | name | string | | Yes | | permission_keys | [ string ] | | Yes | | role | string | | No | -| site | [AppDetailSiteResponse](#appdetailsiteresponse) | | No | -| tags | [ [Tag](#tag) ] | | No | -| tracing | | | No | -| updated_at | integer | | No | -| updated_by | string | | No | -| use_icon_as_answer_icon | boolean | | No | -| workflow | [WorkflowPartial](#workflowpartial) | | No | +| site | [AppDetailSiteResponse](#appdetailsiteresponse) | | Yes | +| tags | [ [Tag](#tag) ] | | Yes | +| tracing | string | | Yes | +| updated_at | integer | | Yes | +| updated_by | string | | Yes | +| use_icon_as_answer_icon | boolean | | Yes | +| workflow | [WorkflowPartial](#workflowpartial) | | Yes | #### AgentAppFeaturesPayload @@ -13752,7 +13752,7 @@ default (the config form sends the full desired feature state on save). | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| access_mode | string | | No | +| access_mode | [WebAppAccessMode](#webappaccessmode) | | No | | active_config_is_published | boolean | | No | | app_id | string | | No | | author_name | string | | No | @@ -13767,13 +13767,13 @@ default (the config form sends the full desired feature state on save). | hidden_app_backed | boolean | | No | | icon | string | | No | | icon_background | string | | No | -| icon_type | string | | No | +| icon_type | [IconType](#icontype) | | No | | icon_url | string | | Yes | | id | string | | Yes | | is_starred | boolean | | No | | maintainer | string | | No | | max_active_requests | integer | | No | -| mode | string | | Yes | +| mode | [AppMode](#appmode) | | Yes | | model_config | [ModelConfigPartial](#modelconfigpartial) | | No | | name | string | | Yes | | permission_keys | [ string ] | | Yes | @@ -15357,7 +15357,7 @@ Legacy Chat App model config used only for follow-up question generation. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| autoPlay | string | | No | +| autoPlay | string,
**Available values:** "disabled", "enabled" | | No | | enabled | boolean | | No | | language | string | | No | | voice | string | | No | @@ -15742,99 +15742,220 @@ This class is used to store the schema information of an api based tool. | app_id | string | | No | | items | [ [AccessMatrixItem](#accessmatrixitem) ] | | No | +#### AppAgentModeResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| enabled | boolean | | Yes | +| max_iteration | integer | | No | +| prompt | [AppAgentPromptResponse](#appagentpromptresponse)
string | | No | +| strategy | [PlanningStrategy](#planningstrategy)
string,
**Available values:** "cot", "function-calling" | | No | +| tools | [ [AppProviderAgentToolResponse](#appprovideragenttoolresponse)
[AppLegacyDatasetToolResponse](#applegacydatasettoolresponse)
[AppLegacyGoogleSearchToolResponse](#applegacygooglesearchtoolresponse)
[AppLegacyWebReaderToolResponse](#applegacywebreadertoolresponse)
[AppLegacyWikipediaToolResponse](#applegacywikipediatoolresponse)
[AppLegacyCurrentDatetimeToolResponse](#applegacycurrentdatetimetoolresponse)
[AppLegacySensitiveWordToolResponseItem](#applegacysensitivewordtoolresponseitem) ] | | No | + +#### AppAgentPromptResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| first_prompt | string | | No | +| next_iteration | string | | No | + +#### AppAnnotationReplyDisabledResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| enabled | boolean | | Yes | + +#### AppAnnotationReplyEnabledResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| embedding_model | [AppEmbeddingModelResponse](#appembeddingmodelresponse) | | Yes | +| enabled | boolean | | Yes | +| id | string | | Yes | +| score_threshold | number | | Yes | + #### AppApiStatusPayload | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | enable_api | boolean | Enable or disable API | Yes | +#### AppChatPromptConfigResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| prompt | [ [AppChatPromptMessageResponse](#appchatpromptmessageresponse) ] | | No | + +#### AppChatPromptMessageResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| role | string | | Yes | +| text | string | | Yes | + +#### AppCheckboxFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| checkbox | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + +#### AppCompletionPromptConfigResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| conversation_histories_role | [AppConversationHistoriesRoleResponse](#appconversationhistoriesroleresponse) | | No | +| prompt | [AppCompletionPromptTextResponse](#appcompletionprompttextresponse) | | No | + +#### AppCompletionPromptTextResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| text | string | | Yes | + +#### AppConversationHistoriesRoleResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| assistant_prefix | string | | Yes | +| user_prefix | string | | Yes | + +#### AppDatasetConfigsResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| datasets | [AppDatasetListResponse](#appdatasetlistresponse) | | No | +| metadata_filtering_conditions | [AppMetadataFilteringConditionsResponse](#appmetadatafilteringconditionsresponse) | | No | +| metadata_filtering_mode | string,
**Available values:** "automatic", "disabled", "manual" | *Enum:* `"automatic"`, `"disabled"`, `"manual"` | No | +| metadata_model_config | [AppModelSelectionResponse](#appmodelselectionresponse) | | No | +| reranking_enable | boolean | | No | +| reranking_enabled | boolean | | No | +| reranking_mode | [RerankMode](#rerankmode) | | No | +| reranking_model | [AppRerankingModelResponse](#apprerankingmodelresponse) | | No | +| retrieval_model | string,
**Available values:** "multiple", "single" | *Enum:* `"multiple"`, `"single"` | Yes | +| score_threshold | number | | No | +| score_threshold_enabled | boolean | | No | +| top_k | integer | | No | +| weights | [AppWeightsResponse](#appweightsresponse) | | No | + +#### AppDatasetItemResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| dataset | [AppDatasetReferenceResponse](#appdatasetreferenceresponse) | | Yes | + +#### AppDatasetListResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| datasets | [ [AppDatasetItemResponse](#appdatasetitemresponse) ] | | Yes | +| strategy | string | | No | + +#### AppDatasetReferenceResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| enabled | boolean | | No | +| id | string | | No | + #### AppDetail | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| access_mode | string | | No | -| created_at | integer | | No | -| created_by | string | | No | -| description | string | | No | +| access_mode | [WebAppAccessMode](#webappaccessmode) | | Yes | +| created_at | integer | | Yes | +| created_by | string | | Yes | +| description | string | | Yes | | enable_api | boolean | | Yes | | enable_site | boolean | | Yes | -| icon | string | | No | -| icon_background | string | | No | +| icon | string | | Yes | +| icon_background | string | | Yes | | id | string | | Yes | -| maintainer | string | | No | -| mode | string | | Yes | -| model_config | [AppModelConfigResponse](#appmodelconfigresponse) | | No | +| maintainer | string | | Yes | +| mode | [AppMode](#appmode) | | Yes | +| model_config | [AppModelConfigResponse](#appmodelconfigresponse) | | Yes | | name | string | | Yes | -| permission_keys | [ string ] | | No | -| tags | [ [Tag](#tag) ] | | No | -| tracing | | | No | -| updated_at | integer | | No | -| updated_by | string | | No | -| use_icon_as_answer_icon | boolean | | No | -| workflow | [WorkflowPartial](#workflowpartial) | | No | +| permission_keys | [ string ] | | Yes | +| tags | [ [Tag](#tag) ] | | Yes | +| tracing | string | | Yes | +| updated_at | integer | | Yes | +| updated_by | string | | Yes | +| use_icon_as_answer_icon | boolean | | Yes | +| workflow | [WorkflowPartial](#workflowpartial) | | Yes | #### AppDetailSiteResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| access_token | string | | No | -| app_base_url | string | | No | -| chat_color_theme | string | | No | -| chat_color_theme_inverted | boolean | | No | -| code | string | | No | -| copyright | string | | No | -| created_at | integer | | No | -| created_by | string | | No | -| custom_disclaimer | string | | No | -| customize_domain | string | | No | -| customize_token_strategy | string | | No | -| default_language | string | | No | -| description | string | | No | -| icon | string | | No | -| icon_background | string | | No | -| icon_type | string
[IconType](#icontype) | | No | +| access_token | string | | Yes | +| app_base_url | string | | Yes | +| chat_color_theme | string | | Yes | +| chat_color_theme_inverted | boolean | | Yes | +| code | string | | Yes | +| copyright | string | | Yes | +| created_at | integer | | Yes | +| created_by | string | | Yes | +| custom_disclaimer | string | | Yes | +| customize_domain | string | | Yes | +| customize_token_strategy | [CustomizeTokenStrategy](#customizetokenstrategy) | | Yes | +| default_language | string | | Yes | +| description | string | | Yes | +| icon | string | | Yes | +| icon_background | string | | Yes | +| icon_type | [IconType](#icontype) | | Yes | | icon_url | string | | Yes | -| input_placeholder | string | | No | -| privacy_policy | string | | No | -| prompt_public | boolean | | No | -| show_workflow_steps | boolean | | No | -| title | string | | No | -| updated_at | integer | | No | -| updated_by | string | | No | -| use_icon_as_answer_icon | boolean | | No | +| input_placeholder | string | | Yes | +| privacy_policy | string | | Yes | +| prompt_public | boolean | | Yes | +| show_workflow_steps | boolean | | Yes | +| title | string | | Yes | +| updated_at | integer | | Yes | +| updated_by | string | | Yes | +| use_icon_as_answer_icon | boolean | | Yes | #### AppDetailWithSite | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| access_mode | string | | No | -| api_base_url | string | | No | -| app_id | string | | No | -| bound_agent_id | string | | No | -| created_at | integer | | No | -| created_by | string | | No | -| deleted_tools | [ [DeletedTool](#deletedtool) ] | | No | -| description | string | | No | +| access_mode | [WebAppAccessMode](#webappaccessmode) | | Yes | +| api_base_url | string | | Yes | +| app_id | string | | Yes | +| bound_agent_id | string | | Yes | +| created_at | integer | | Yes | +| created_by | string | | Yes | +| deleted_tools | [ [DeletedTool](#deletedtool) ] | | Yes | +| description | string | | Yes | | enable_api | boolean | | Yes | | enable_site | boolean | | Yes | -| icon | string | | No | -| icon_background | string | | No | -| icon_type | string | | No | +| icon | string | | Yes | +| icon_background | string | | Yes | +| icon_type | [IconType](#icontype) | | Yes | | icon_url | string | | Yes | | id | string | | Yes | -| maintainer | string | | No | -| max_active_requests | integer | | No | -| mode | string | | Yes | -| model_config | [AppModelConfigResponse](#appmodelconfigresponse) | | No | +| maintainer | string | | Yes | +| max_active_requests | integer | | Yes | +| mode | [AppMode](#appmode) | | Yes | +| model_config | [AppModelConfigResponse](#appmodelconfigresponse) | | Yes | | name | string | | Yes | -| permission_keys | [ string ] | | No | -| site | [AppDetailSiteResponse](#appdetailsiteresponse) | | No | -| tags | [ [Tag](#tag) ] | | No | -| tracing | | | No | -| updated_at | integer | | No | -| updated_by | string | | No | -| use_icon_as_answer_icon | boolean | | No | -| workflow | [WorkflowPartial](#workflowpartial) | | No | +| permission_keys | [ string ] | | Yes | +| site | [AppDetailSiteResponse](#appdetailsiteresponse) | | Yes | +| tags | [ [Tag](#tag) ] | | Yes | +| tracing | string | | Yes | +| updated_at | integer | | Yes | +| updated_by | string | | Yes | +| use_icon_as_answer_icon | boolean | | Yes | +| workflow | [WorkflowPartial](#workflowpartial) | | Yes | + +#### AppDisabledExternalDataToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| config | object | | No | +| enabled | boolean | | Yes | +| icon | string | | No | +| icon_background | string | | No | +| label | string | | No | +| type | string | | No | +| variable | string | | No | #### AppDslVersionResponse @@ -15842,6 +15963,31 @@ This class is used to store the schema information of an api based tool. | ---- | ---- | ----------- | -------- | | app_dsl_version | string | | Yes | +#### AppEmbeddingModelResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| embedding_model_name | string | | Yes | +| embedding_provider_name | string | | Yes | + +#### AppEnabledConfigResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| enabled | boolean | | Yes | + +#### AppEnabledExternalDataToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| config | object | | Yes | +| enabled | boolean | | Yes | +| icon | string | | No | +| icon_background | string | | No | +| label | string | | No | +| type | string | | Yes | +| variable | string | | Yes | + #### AppExportQuery | Name | Type | Description | Required | @@ -15857,6 +16003,35 @@ This class is used to store the schema information of an api based tool. | ---- | ---- | ----------- | -------- | | data | string | | Yes | +#### AppExternalDataToolFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| external_data_tool | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + +#### AppFileFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| file | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + +#### AppFileListFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| file-list | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + +#### AppFileUploadResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| allowed_file_extensions | [ string ] | | No | +| allowed_file_types | [ [FileType](#filetype) ] | | No | +| allowed_file_upload_methods | [ [FileTransferMethod](#filetransfermethod) ] | | No | +| enabled | boolean | | No | +| image | [AppImageUploadResponse](#appimageuploadresponse) | | No | +| number_limits | integer | | No | + #### AppIconPayload | Name | Type | Description | Required | @@ -15865,6 +16040,15 @@ This class is used to store the schema information of an api based tool. | icon_background | string | Icon background color | No | | icon_type | [IconType](#icontype) | Icon type | No | +#### AppImageUploadResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| detail | string,
**Available values:** "high", "low" | | No | +| enabled | boolean | | No | +| number_limits | integer | | No | +| transfer_methods | [ [FileTransferMethod](#filetransfermethod) ] | | No | + #### AppImportPayload | Name | Type | Description | Required | @@ -15884,7 +16068,7 @@ This class is used to store the schema information of an api based tool. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | app_id | string | | No | -| app_mode | string | | No | +| app_mode | [AppMode](#appmode) | | No | | current_dsl_version | string | | Yes | | error | string | | No | | id | string | | Yes | @@ -15892,6 +16076,62 @@ This class is used to store the schema information of an api based tool. | status | [ImportStatus](#importstatus) | | Yes | | warnings | [ [DslImportWarning](#dslimportwarning) ] | | No | +#### AppJsonObjectFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| json_object | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + +#### AppKeywordSettingResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| keyword_weight | number | | Yes | + +#### AppLegacyCurrentDatetimeToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| current_datetime | object | | Yes | + +#### AppLegacyDatasetToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| dataset | [AppDatasetReferenceResponse](#appdatasetreferenceresponse) | | Yes | + +#### AppLegacyGoogleSearchToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| google_search | object | | Yes | + +#### AppLegacySensitiveWordToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| canned_response | string | | Yes | +| enabled | boolean | | Yes | +| words | [ string ] | | Yes | + +#### AppLegacySensitiveWordToolResponseItem + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| sensitive-word-avoidance | [AppLegacySensitiveWordToolResponse](#applegacysensitivewordtoolresponse) | | Yes | + +#### AppLegacyWebReaderToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| web_reader | object | | Yes | + +#### AppLegacyWikipediaToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| wikipedia | object | | Yes | + #### AppListQuery | Name | Type | Description | Required | @@ -15926,6 +16166,23 @@ AppMCPServer Status Enum | ---- | ---- | ----------- | -------- | | AppMCPServerStatus | string | AppMCPServer Status Enum | | +#### AppMetadataConditionResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| comparison_operator | string,
**Available values:** "<", "=", ">", "after", "before", "contains", "empty", "end with", "in", "is", "is not", "not contains", "not empty", "not in", "start with", "≠", "≤", "≥" | *Enum:* `"<"`, `"="`, `">"`, `"after"`, `"before"`, `"contains"`, `"empty"`, `"end with"`, `"in"`, `"is"`, `"is not"`, `"not contains"`, `"not empty"`, `"not in"`, `"start with"`, `"≠"`, `"≤"`, `"≥"` | Yes | +| id | string | | No | +| metadata_id | string | | No | +| name | string | | Yes | +| value | string
[ string ]
integer
number | | No | + +#### AppMetadataFilteringConditionsResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| conditions | [ [AppMetadataConditionResponse](#appmetadataconditionresponse) ] | | No | +| logical_operator | string,
**Available values:** "and", "or" | | No | + #### AppMode | Name | Type | Description | Required | @@ -15936,30 +16193,39 @@ AppMCPServer Status Enum | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| agent_mode | | | No | -| annotation_reply | | | No | -| chat_prompt_config | | | No | -| completion_prompt_config | | | No | -| created_at | integer | | No | -| created_by | string | | No | -| dataset_configs | | | No | -| dataset_query_variable | string | | No | -| external_data_tools | | | No | -| file_upload | | | No | -| model | | | No | -| more_like_this | | | No | -| opening_statement | string | | No | -| pre_prompt | string | | No | -| prompt_type | string | | No | -| retriever_resource | | | No | -| sensitive_word_avoidance | | | No | -| speech_to_text | | | No | -| suggested_questions | | | No | -| suggested_questions_after_answer | | | No | -| text_to_speech | | | No | -| updated_at | integer | | No | -| updated_by | string | | No | -| user_input_form | | | No | +| agent_mode | [AppAgentModeResponse](#appagentmoderesponse) | | Yes | +| annotation_reply | [AppAnnotationReplyEnabledResponse](#appannotationreplyenabledresponse)
[AppAnnotationReplyDisabledResponse](#appannotationreplydisabledresponse) | | Yes | +| chat_prompt_config | [AppChatPromptConfigResponse](#appchatpromptconfigresponse) | | Yes | +| completion_prompt_config | [AppCompletionPromptConfigResponse](#appcompletionpromptconfigresponse) | | Yes | +| created_at | integer | | Yes | +| created_by | string | | Yes | +| dataset_configs | [AppDatasetConfigsResponse](#appdatasetconfigsresponse) | | Yes | +| dataset_query_variable | string | | Yes | +| external_data_tools | [ [AppEnabledExternalDataToolResponse](#appenabledexternaldatatoolresponse)
[AppDisabledExternalDataToolResponse](#appdisabledexternaldatatoolresponse) ] | | Yes | +| file_upload | [AppFileUploadResponse](#appfileuploadresponse) | | Yes | +| model | [AppModelSelectionResponse](#appmodelselectionresponse) | | Yes | +| more_like_this | [AppEnabledConfigResponse](#appenabledconfigresponse) | | Yes | +| opening_statement | string | | Yes | +| pre_prompt | string | | Yes | +| prompt_type | [PromptType](#prompttype) | | Yes | +| retriever_resource | [AppEnabledConfigResponse](#appenabledconfigresponse) | | Yes | +| sensitive_word_avoidance | [AppSensitiveWordAvoidanceResponse](#appsensitivewordavoidanceresponse) | | Yes | +| speech_to_text | [AppEnabledConfigResponse](#appenabledconfigresponse) | | Yes | +| suggested_questions | [ string ] | | Yes | +| suggested_questions_after_answer | [AppSuggestedQuestionsAfterAnswerResponse](#appsuggestedquestionsafteranswerresponse) | | Yes | +| text_to_speech | [AppTextToSpeechResponse](#apptexttospeechresponse) | | Yes | +| updated_at | integer | | Yes | +| updated_by | string | | Yes | +| user_input_form | [ [AppTextInputFormResponse](#apptextinputformresponse)
[AppSelectFormResponse](#appselectformresponse)
[AppParagraphFormResponse](#appparagraphformresponse)
[AppNumberFormResponse](#appnumberformresponse)
[AppCheckboxFormResponse](#appcheckboxformresponse)
[AppFileFormResponse](#appfileformresponse)
[AppFileListFormResponse](#appfilelistformresponse)
[AppExternalDataToolFormResponse](#appexternaldatatoolformresponse)
[AppJsonObjectFormResponse](#appjsonobjectformresponse) ] | | Yes | + +#### AppModelSelectionResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| completion_params | object | | No | +| mode | [LLMMode](#llmmode)
string | | No | +| name | string | | No | +| provider | string | | No | #### AppNamePayload @@ -15967,6 +16233,12 @@ AppMCPServer Status Enum | ---- | ---- | ----------- | -------- | | name | string | Name to check | Yes | +#### AppNumberFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| number | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + #### AppPagination | Name | Type | Description | Required | @@ -15977,11 +16249,17 @@ AppMCPServer Status Enum | page | integer | | Yes | | total | integer | | Yes | +#### AppParagraphFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| paragraph | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + #### AppPartial | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| access_mode | string | | No | +| access_mode | [WebAppAccessMode](#webappaccessmode) | | No | | app_id | string | | No | | author_name | string | | No | | bound_agent_id | string | | No | @@ -15992,13 +16270,13 @@ AppMCPServer Status Enum | has_draft_trigger | boolean | | No | | icon | string | | No | | icon_background | string | | No | -| icon_type | string | | No | +| icon_type | [IconType](#icontype) | | No | | icon_url | string | | Yes | | id | string | | Yes | | is_starred | boolean | | No | | maintainer | string | | No | | max_active_requests | integer | | No | -| mode | string | | Yes | +| mode | [AppMode](#appmode) | | Yes | | model_config | [ModelConfigPartial](#modelconfigpartial) | | No | | name | string | | Yes | | permission_keys | [ string ] | | No | @@ -16008,12 +16286,50 @@ AppMCPServer Status Enum | use_icon_as_answer_icon | boolean | | No | | workflow | [WorkflowPartial](#workflowpartial) | | No | +#### AppProviderAgentToolResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| credential_id | string | | No | +| enabled | boolean | | No | +| isDeleted | boolean | | No | +| notAuthor | boolean | | No | +| plugin_unique_identifier | string | | No | +| provider_id | string | | Yes | +| provider_name | string | | No | +| provider_type | [ToolProviderType](#toolprovidertype) | | Yes | +| tool_label | string | | No | +| tool_name | string | | Yes | +| tool_parameters | object | | Yes | + +#### AppRerankingModelResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| reranking_model_name | string | | No | +| reranking_provider_name | string | | No | + +#### AppSelectFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| select | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + #### AppSelectorScope | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | AppSelectorScope | string | | | +#### AppSensitiveWordAvoidanceResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| config | object | | No | +| configs | [ [JsonValue](#jsonvalue) ] | | No | +| enabled | boolean | | Yes | +| type | string | | No | + #### AppSiteResponse | Name | Type | Description | Required | @@ -16063,6 +16379,29 @@ AppMCPServer Status Enum | title | string | | No | | use_icon_as_answer_icon | boolean | | No | +#### AppSuggestedQuestionsAfterAnswerResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| enabled | boolean | | Yes | +| model | [AppModelSelectionResponse](#appmodelselectionresponse) | | No | +| prompt | string | | No | + +#### AppTextInputFormResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| text-input | [AppUserInputFormConfigResponse](#appuserinputformconfigresponse) | | Yes | + +#### AppTextToSpeechResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| autoPlay | string,
**Available values:** "disabled", "enabled" | *Enum:* `"disabled"`, `"enabled"` | No | +| enabled | boolean | | Yes | +| language | string | | No | +| voice | string | | No | + #### AppTracePayload | Name | Type | Description | Required | @@ -16077,6 +16416,28 @@ AppMCPServer Status Enum | enabled | boolean | | No | | tracing_provider | string | | No | +#### AppUserInputFormConfigResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| allowed_file_extensions | [ string ] | | No | +| allowed_file_types | [ [FileType](#filetype) ] | | No | +| allowed_file_upload_methods | [ [FileTransferMethod](#filetransfermethod) ] | | No | +| config | object | | No | +| default | [JsonValue](#jsonvalue) | | No | +| description | string | | No | +| enabled | boolean | | No | +| hide | boolean | | No | +| icon | string | | No | +| icon_background | string | | No | +| json_schema | string
object | | No | +| label | string | | Yes | +| max_length | integer | | No | +| options | [ string ] | | No | +| required | boolean | | No | +| type | string | | No | +| variable | string | | Yes | + #### AppVariableConfig | Name | Type | Description | Required | @@ -16086,6 +16447,22 @@ AppMCPServer Status Enum | required | boolean | | No | | type | string | | Yes | +#### AppVectorSettingResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| embedding_model_name | string | | Yes | +| embedding_provider_name | string | | Yes | +| vector_weight | number | | Yes | + +#### AppWeightsResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| keyword_setting | [AppKeywordSettingResponse](#appkeywordsettingresponse) | | Yes | +| vector_setting | [AppVectorSettingResponse](#appvectorsettingresponse) | | Yes | +| weight_type | string,
**Available values:** "customized", "keyword_first", "semantic_first" | *Enum:* `"customized"`, `"keyword_first"`, `"semantic_first"` | No | + #### AudioBinaryResponse | Name | Type | Description | Required | @@ -16977,6 +17354,14 @@ Model class for provider custom model configuration. | model_type | [ModelType](#modeltype) | | Yes | | unadded_to_model_list | boolean | | No | +#### CustomizeTokenStrategy + +Site token customization strategy + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| CustomizeTokenStrategy | string | Site token customization strategy | | + #### CustomizedPipelineTemplatePayload | Name | Type | Description | Required | @@ -19050,7 +19435,7 @@ How Dify forwards the end-user's identity to an MCP server. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | app_id | string | | No | -| app_mode | string | | No | +| app_mode | [AppMode](#appmode) | | No | | current_dsl_version | string,
**Default:** 0.7.0 | | No | | error | string | | No | | id | string | | Yes | @@ -19707,7 +20092,7 @@ Metadata operation data | ---- | ---- | ----------- | -------- | | created_at | integer | | No | | created_by | string | | No | -| model | | | No | +| model | [AppModelSelectionResponse](#appmodelselectionresponse) | | No | | pre_prompt | string | | No | | updated_at | integer | | No | | updated_by | string | | No | @@ -20755,6 +21140,12 @@ Shared permission levels for resources (datasets, credentials, etc.) | unit | string | | No | | variable | string | | Yes | +#### PlanningStrategy + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| PlanningStrategy | string | | | + #### PluginAutoUpgradeChangeResponse | Name | Type | Description | Required | @@ -21263,6 +21654,14 @@ Dataset Process Rule Mode | mode | [ProcessRuleMode](#processrulemode) | | Yes | | rules | [Rule](#rule) | | No | +#### PromptType + +Prompt configuration type + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| PromptType | string | Prompt configuration type | | + #### ProviderConfig Model class for common provider settings like credentials @@ -21755,6 +22154,12 @@ Resource types understood by access policies. | request | object | The request of the request log | Yes | | response | object | The response of the request log | Yes | +#### RerankMode + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| RerankMode | string | | | + #### RerankingModel | Name | Type | Description | Required | @@ -24022,6 +24427,12 @@ in form definition, or a variable while the workflow is running. | is_valid | boolean | | Yes | | token | string | | Yes | +#### WebAppAccessMode + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| WebAppAccessMode | string | | | + #### WebAppAuthModel | Name | Type | Description | Required | @@ -25112,7 +25523,7 @@ Query parameters for workflow runs. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| autoPlay | string | | No | +| autoPlay | string,
**Available values:** "disabled", "enabled" | | No | | enabled | boolean | | No | | language | string | | No | | voice | string | | No | diff --git a/api/openapi/markdown/openapi-openapi.md b/api/openapi/markdown/openapi-openapi.md index 385be3dc5dc..388e8e2c169 100644 --- a/api/openapi/markdown/openapi-openapi.md +++ b/api/openapi/markdown/openapi-openapi.md @@ -694,7 +694,7 @@ Request body for POST /workspaces//apps/imports. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | app_id | string | | No | -| app_mode | string | | No | +| app_mode | [AppMode](#appmode) | | No | | current_dsl_version | string,
**Default:** 0.7.0 | | No | | error | string | | No | | hints | [ [Hint](#hint) ] | Next steps the caller can take | No | @@ -971,7 +971,7 @@ A next step the caller can hand straight to `call --input `. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | app_id | string | | No | -| app_mode | string | | No | +| app_mode | [AppMode](#appmode) | | No | | current_dsl_version | string,
**Default:** 0.7.0 | | No | | error | string | | No | | id | string | | Yes | diff --git a/api/services/app/console_service.py b/api/services/app/console_service.py index b4929e78f9a..424f70b6bc3 100644 --- a/api/services/app/console_service.py +++ b/api/services/app/console_service.py @@ -8,6 +8,7 @@ from typing import BinaryIO, Literal, Protocol from uuid import UUID, uuid4 from machinery.context import RequestContext +from models.model import AppMode from services.agent.errors import InvalidRosterAgentPackageError from services.agent.roster_package_entities import RosterAgentPackageExport from services.entities.app_entities import ( @@ -228,7 +229,7 @@ class ConsoleAppService: id=str(uuid4()), status=ImportStatus.COMPLETED_WITH_WARNINGS if result.warnings else ImportStatus.COMPLETED, app_id=result.app_id, - app_mode="agent", + app_mode=AppMode.AGENT, warnings=result.warnings, ) @@ -300,7 +301,7 @@ class ConsoleAppService: def update(self, context: RequestContext, app_id: str, params: UpdateAppParams) -> AppRecord: app = self._apps.update(context, app_id, params) self._lifecycle.updated(context, app) - return self._lifecycle.present(context, app) + return self.get(context, app_id) def rename(self, context: RequestContext, app_id: str, name: str) -> AppRecord: app = self._apps.rename(context, app_id, name) diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py index 10be610f172..65e2622b3c9 100644 --- a/api/services/app_dsl_service.py +++ b/api/services/app_dsl_service.py @@ -17,6 +17,10 @@ from sqlalchemy.orm import Session from configs import dify_config from constants.dsl_version import CURRENT_APP_DSL_VERSION +from core.app.app_config.features.suggested_questions_after_answer.manager import ( + SuggestedQuestionsAfterAnswerConfigManager, +) +from core.app.app_config.features.text_to_speech.manager import TextToSpeechConfigManager from core.file import remote_fetcher from core.plugin.entities.plugin import PluginDependency from core.rbac import RBACPermission, RBACResourceScope @@ -694,6 +698,8 @@ class AppDslService: model_config = data.get("model_config") if not model_config or not isinstance(model_config, dict): raise ValueError("Missing model_config for chat/agent-chat/completion app") + SuggestedQuestionsAfterAnswerConfigManager.validate_optional_fields(model_config) + TextToSpeechConfigManager.validate_optional_fields(model_config) # Initialize or update model config app_model_config = ( self._session.get(AppModelConfig, app.app_model_config_id) if app.app_model_config_id else None diff --git a/api/services/app_service.py b/api/services/app_service.py index 642e188f79a..7510d6b3e95 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -1,9 +1,10 @@ import json import logging from collections.abc import Callable, Mapping, Sequence +from copy import deepcopy from dataclasses import dataclass from datetime import datetime -from typing import Any, NotRequired, TypedDict, cast, override +from typing import Any, NotRequired, TypedDict, cast import sqlalchemy as sa from pydantic import JsonValue @@ -120,9 +121,13 @@ class AppModelConfigResponseView: class AppResponseView: """Expose App response properties through one caller-owned database session.""" - def __init__(self, app: App, *, session: Session) -> None: + def __init__( + self, app: App, *, session: Session, account: Account | None = None, access_mode: str | None = None + ) -> None: self._app = app self._session = session + self._account = account + self._access_mode = access_mode def __getattr__(self, name: str) -> Any: return getattr(self._app, name) # guard-ignore: no-new-getattr -- delegates model fields @@ -140,6 +145,19 @@ class AppResponseView: app_model_config = self._app.app_model_config_with_session(session=self._session) if app_model_config is None: return None + if self._account is not None and ( + self._app.mode == AppMode.AGENT_CHAT or self._app.is_agent_with_session(session=self._session) + ): + tenant_id = self._account.current_tenant_id + assert tenant_id is not None + masked_agent_mode = mask_agent_tool_parameters( + agent_mode=cast(Mapping[str, JsonValue], app_model_config.agent_mode_dict), + app_id=self._app.id, + tenant_id=tenant_id, + user_id=self._account.id, + ) + app_model_config = deepcopy(app_model_config) + app_model_config.agent_mode = json.dumps(masked_agent_mode) return AppModelConfigResponseView(app_model_config, session=self._session) @property @@ -154,6 +172,18 @@ class AppResponseView: def mode_compatible_with_agent(self) -> str: return self._app.mode_compatible_with_agent_with_session(session=self._session) + @property + def access_mode(self) -> str | None: + return self._access_mode + + @property + def permission_keys(self) -> list[str]: + return [] + + @property + def app_id(self) -> str | None: + return None + @property def deleted_tools(self) -> list[Any]: return self._app.deleted_tools_with_session(session=self._session) @@ -580,48 +610,6 @@ class AppService: if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD: BillingService.clean_billing_info_cache(app.tenant_id) - def get_app(self, app: App, *, session: Session) -> App: - """ - Get App - """ - assert isinstance(current_user, Account) - assert current_user.current_tenant_id is not None - # get original app model config - if app.mode == AppMode.AGENT_CHAT or app.is_agent_with_session(session=session): - model_config = app.app_model_config_with_session(session=session) - if not model_config: - return app - agent_mode = mask_agent_tool_parameters( - agent_mode=cast(Mapping[str, JsonValue], model_config.agent_mode_dict), - app_id=app.id, - tenant_id=current_user.current_tenant_id, - user_id=current_user.id, - ) - - # override agent mode - if model_config: - model_config.agent_mode = json.dumps(agent_mode) - - class ModifiedApp(App): - """ - Modified App class - """ - - def __init__(self, app): - self.__dict__.update(app.__dict__) - - @override - def app_model_config_with_session(self, *, session: Session) -> AppModelConfig | None: - # Hand back the in-memory config the masking pass above produced, and - # deliberately ignore `session`: re-reading the row here would undo the - # masking. Response paths resolve the config through this accessor - # (`AppResponseView.app_model_config`), so the override has to sit here. - return model_config - - app = ModifiedApp(app) - - return app - class ArgsDict(TypedDict): name: str description: str diff --git a/api/services/audio_service.py b/api/services/audio_service.py index bf33746296c..07fd904ccde 100644 --- a/api/services/audio_service.py +++ b/api/services/audio_service.py @@ -10,7 +10,6 @@ import logging import uuid from collections.abc import Iterable from dataclasses import dataclass -from typing import cast from flask import Response, stream_with_context from sqlalchemy import select @@ -269,7 +268,7 @@ class AudioService: if not text_to_speech_dict.get("enabled"): raise ValueError("TTS is not enabled") - voice = cast(str | None, text_to_speech_dict.get("voice")) + voice = text_to_speech_dict.get("voice") return PreparedTextToAudio(text=text, voice=voice) diff --git a/api/services/entities/dsl_entities.py b/api/services/entities/dsl_entities.py index cf596114078..67272f1120a 100644 --- a/api/services/entities/dsl_entities.py +++ b/api/services/entities/dsl_entities.py @@ -82,7 +82,7 @@ class Import(BaseModel): id: str status: ImportStatus app_id: str | None = None - app_mode: str | None = None + app_mode: AppMode | None = None permission_keys: list[str] = Field(default_factory=list) current_dsl_version: str = CURRENT_APP_DSL_VERSION imported_dsl_version: str = "" diff --git a/api/tests/test_containers_integration_tests/controllers/console/app/test_app_detail_config_integrity.py b/api/tests/test_containers_integration_tests/controllers/console/app/test_app_detail_config_integrity.py new file mode 100644 index 00000000000..7ce218bba19 --- /dev/null +++ b/api/tests/test_containers_integration_tests/controllers/console/app/test_app_detail_config_integrity.py @@ -0,0 +1,86 @@ +"""App detail responses must not write masked configuration back to storage.""" + +import json + +from flask.testing import FlaskClient +from sqlalchemy.orm import Session + +from models.model import AppMode, AppModelConfig +from tests.test_containers_integration_tests.controllers.console.helpers import ( + authenticate_console_client, + create_console_account_and_tenant, + create_console_app, +) + + +def test_put_app_detail_preserves_agent_tool_secret_after_commit( + db_session_with_containers: Session, + test_client_with_containers: FlaskClient, +) -> None: + account, tenant = create_console_account_and_tenant(db_session_with_containers) + app = create_console_app(db_session_with_containers, tenant.id, account.id, AppMode.AGENT_CHAT) + agent_mode = { + "enabled": True, + "strategy": "react", + "tools": [ + { + "provider_type": "api", + "provider_id": "00000000-0000-0000-0000-000000000001", + "tool_name": "private_tool", + "tool_parameters": {"api_key": "encrypted-secret"}, + } + ], + } + config = AppModelConfig(app_id=app.id, agent_mode=json.dumps(agent_mode)) + db_session_with_containers.add(config) + db_session_with_containers.flush() + app.app_model_config_id = config.id + db_session_with_containers.commit() + + headers = authenticate_console_client(test_client_with_containers, account) + response = test_client_with_containers.put( + f"/console/api/apps/{app.id}", + headers=headers, + json={"name": "Updated App"}, + ) + + assert response.status_code == 200 + assert response.json is not None + assert response.json["model_config"]["agent_mode"]["tools"][0]["tool_parameters"] != {"api_key": "encrypted-secret"} + with Session(db_session_with_containers.get_bind()) as reread_session: + persisted = reread_session.get(AppModelConfig, config.id) + assert persisted is not None + assert persisted.agent_mode is not None + assert json.loads(persisted.agent_mode) == agent_mode + + +def test_get_and_put_app_detail_preserve_historical_agent_mode_without_tools( + db_session_with_containers: Session, + test_client_with_containers: FlaskClient, +) -> None: + account, tenant = create_console_account_and_tenant(db_session_with_containers) + app = create_console_app(db_session_with_containers, tenant.id, account.id, AppMode.AGENT_CHAT) + agent_mode = {"enabled": True, "max_iteration": 5, "strategy": "function_call"} + config = AppModelConfig(app_id=app.id, agent_mode=json.dumps(agent_mode)) + db_session_with_containers.add(config) + db_session_with_containers.flush() + app.app_model_config_id = config.id + db_session_with_containers.commit() + + headers = authenticate_console_client(test_client_with_containers, account) + get_response = test_client_with_containers.get(f"/console/api/apps/{app.id}", headers=headers) + assert get_response.status_code == 200 + assert get_response.json is not None + assert get_response.json["model_config"]["agent_mode"] == agent_mode + + put_response = test_client_with_containers.put( + f"/console/api/apps/{app.id}", headers=headers, json={"name": "Updated App"} + ) + assert put_response.status_code == 200 + assert put_response.json is not None + assert put_response.json["model_config"]["agent_mode"] == agent_mode + + with Session(db_session_with_containers.get_bind()) as reread_session: + persisted = reread_session.get(AppModelConfig, config.id) + assert persisted is not None + assert persisted.agent_mode == json.dumps(agent_mode) diff --git a/api/tests/test_containers_integration_tests/services/test_app_service.py b/api/tests/test_containers_integration_tests/services/test_app_service.py index d0aff28dec4..a71371b95d8 100644 --- a/api/tests/test_containers_integration_tests/services/test_app_service.py +++ b/api/tests/test_containers_integration_tests/services/test_app_service.py @@ -151,55 +151,6 @@ class TestAppService: assert app.tenant_id == tenant.id assert app.created_by == account.id - def test_get_app_success(self, db_session_with_containers: Session, mock_external_service_dependencies): - """ - Test successful app retrieval. - """ - fake = Faker() - - # Create account and tenant first - account = AccountService.create_account( - email=fake.email(), - name=fake.name(), - interface_language="en-US", - password=generate_valid_password(fake), - session=db_session_with_containers, - ) - TenantService.create_owner_tenant_if_not_exist(account, name=fake.company(), session=db_session_with_containers) - tenant = account.current_tenant - - # Create app first - # Import here to avoid circular dependency - from services.app_service import AppService, CreateAppParams - - app_params = CreateAppParams( - name=fake.company(), - description=fake.text(max_nb_chars=100), - mode="chat", - icon_type="emoji", - icon="🎯", - icon_background="#45B7D1", - ) - - app_service = AppService() - created_app = app_service.create_app(tenant.id, app_params, account, session=db_session_with_containers) - - # Get app using the service - needs current_user mock - mock_current_user = create_autospec(Account, instance=True) - mock_current_user.id = account.id - mock_current_user.current_tenant_id = account.current_tenant_id - - with patch("services.app_service.current_user", mock_current_user): - retrieved_app = app_service.get_app(created_app, session=db_session_with_containers) - - # Verify retrieved app matches created app - assert retrieved_app.id == created_app.id - assert retrieved_app.name == created_app.name - assert retrieved_app.description == created_app.description - assert retrieved_app.mode == created_app.mode - assert retrieved_app.tenant_id == created_app.tenant_id - assert retrieved_app.created_by == created_app.created_by - def test_get_paginate_apps_success(self, db_session_with_containers: Session, mock_external_service_dependencies): """ Test successful paginated app list retrieval. diff --git a/api/tests/unit_tests/commands/test_generate_swagger_specs.py b/api/tests/unit_tests/commands/test_generate_swagger_specs.py index abbaa1be7ec..5f80554a5dd 100644 --- a/api/tests/unit_tests/commands/test_generate_swagger_specs.py +++ b/api/tests/unit_tests/commands/test_generate_swagger_specs.py @@ -812,6 +812,14 @@ def test_generate_specs_include_console_contract_shapes_for_schema_migration(tmp ) assert {"enabled", "model", "prompt"} <= set(schemas["WorkflowSuggestedQuestionsAfterAnswerPayload"]["properties"]) assert {"enabled", "language", "voice", "autoPlay"} <= set(schemas["WorkflowTextToSpeechPayload"]["properties"]) + assert schemas["WorkflowTextToSpeechPayload"]["properties"]["autoPlay"]["anyOf"][0]["enum"] == [ + "disabled", + "enabled", + ] + assert schemas["AgentTextToSpeechFeatureConfig"]["properties"]["autoPlay"]["anyOf"][0]["enum"] == [ + "disabled", + "enabled", + ] assert {"enabled", "type", "config"} <= set(schemas["WorkflowSensitiveWordAvoidancePayload"]["properties"]) file_upload = schemas["WorkflowFileUploadPayload"]["properties"] assert {"document", "audio", "video", "custom", "preview_config"} <= set(file_upload) 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 17b8254c2db..bb6605dc382 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 @@ -216,9 +216,9 @@ def _app_detail_obj(**overrides) -> App: "tracing": None, "use_icon_as_answer_icon": False, "created_by": "account-1", - "created_at": None, + "created_at": datetime(2025, 1, 1), "updated_by": "account-1", - "updated_at": None, + "updated_at": datetime(2025, 1, 1), "max_active_requests": 0, } overrides.pop("bound_agent_id", None) @@ -369,9 +369,6 @@ def test_agent_app_list_and_create_use_agent_route( apply_config_overrides(monkeypatch, RBAC_ENABLED=True) class FakeAppService: - def get_app(self, app_obj: object, *, session: object) -> object: - return app_obj - def get_paginate_apps(self, user_id: str, tenant_id: str, params, session) -> object: captured["list"] = {"user_id": user_id, "tenant_id": tenant_id, "params": params} return SimpleNamespace( @@ -649,10 +646,6 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id( ) class FakeAppService: - def get_app(self, app_obj: object, *, session: object) -> object: - captured["get_app"] = {"app": app_obj, "session": session} - return app_obj - def update_app(self, app_obj: object, args: dict[str, object], *, session: object) -> object: captured["update"] = {"app": app_obj, "args": args} return _app_detail_obj(id=app_id, tenant_id=tenant_id, name=args["name"], bound_agent_id=agent_id) @@ -674,7 +667,6 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id( assert detail["access_ready"] is False assert "active_config_is_published" not in detail assert "bound_agent_id" not in detail - assert captured["get_app"] == {"app": app_model, "session": session} with app.test_request_context( "/console/api/agent/00000000-0000-0000-0000-000000000001", json={"name": "Renamed", "description": "", "role": "Reviewer", "icon_type": "emoji", "icon": "R"}, @@ -1043,9 +1035,6 @@ def test_agent_app_update_allows_empty_role( ) class FakeAppService: - def get_app(self, app_obj: object, *, session: object) -> object: - return app_obj - def update_app(self, app_obj: object, args: dict[str, object], *, session: object) -> object: captured["update"] = {"app": app_obj, "args": args} return _app_detail_obj(id="app-1", name=args["name"], bound_agent_id=agent_id) diff --git a/api/tests/unit_tests/controllers/console/app/test_app_response_models.py b/api/tests/unit_tests/controllers/console/app/test_app_response_models.py index ff51ca8dd05..ca1425f3947 100644 --- a/api/tests/unit_tests/controllers/console/app/test_app_response_models.py +++ b/api/tests/unit_tests/controllers/console/app/test_app_response_models.py @@ -3,8 +3,10 @@ import json from dataclasses import replace from datetime import datetime +from pathlib import Path import pytest +import yaml from flask import Flask from pydantic import ValidationError from sqlalchemy.orm import Session @@ -12,6 +14,7 @@ from werkzeug.datastructures import MultiDict from controllers.console.app import app as controller from fields import app_fields +from fields.app_model_config_response import AppModelConfigResponse from libs.pagination import PaginatedResult from models.account import Account from models.enums import CustomizeTokenStrategy, TagType @@ -308,7 +311,6 @@ def test_app_detail_with_site_includes_nested_serialization( monkeypatch: pytest.MonkeyPatch, sqlite_session: Session, ): - AppDetailWithSite = app_models.AppDetailWithSite app_obj = _persist_response_graph(sqlite_session) app_obj.name = "Detailed App" app_obj.description = "Desc" @@ -323,16 +325,19 @@ def test_app_detail_with_site_includes_nested_serialization( assert model_config is not None model_config.opening_statement = "hi" model_config.retriever_resource = json.dumps({"enabled": True}) - monkeypatch.setattr("repositories.app.response.load_annotation_reply_config", lambda _session, _app_id: {}) + monkeypatch.setattr( + "repositories.app.response.load_annotation_reply_config", lambda _session, _app_id: {"enabled": False} + ) with app.test_request_context("/"): - serialized = AppDetailWithSite.model_validate( + response = controller._app_detail_response( replace( app_record(app_obj, session=sqlite_session, projection="detail-with-site"), permission_keys=app_obj.permission_keys, ), - from_attributes=True, - ).model_dump(mode="json") + ) + assert isinstance(response, app_models.AppDetailWithSite) + serialized = response.model_dump(mode="json") assert serialized["icon_url"] == "signed:detail-icon" assert serialized["model_config"]["retriever_resource"] == {"enabled": True} @@ -427,3 +432,166 @@ def test_app_pagination_aliases_per_page_and_has_next(app_models, sqlite_session assert len(serialized["data"]) == 2 assert serialized["data"][0]["icon_url"] == "signed:first-icon" assert serialized["data"][1]["icon_url"] is None + + +@pytest.mark.parametrize("strategy", [None, "cot", "function-calling"]) +def test_app_detail_preserves_historical_agent_strategy( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session, strategy: str | None +): + app_obj = _persist_response_graph(sqlite_session) + config = sqlite_session.get(AppModelConfig, CONFIG_ID) + assert config is not None + agent_mode = {"enabled": True, "tools": []} + if strategy is not None: + agent_mode["strategy"] = strategy + config.agent_mode = json.dumps(agent_mode) + sqlite_session.commit() + monkeypatch.setattr( + "repositories.app.response.load_annotation_reply_config", lambda _session, _app_id: {"enabled": False} + ) + + with app.test_request_context("/"): + response = controller._app_detail_response( + app_record(app_obj, session=sqlite_session, projection="detail-with-site") + ).model_dump(mode="json") + + assert response["model_config"]["agent_mode"] == agent_mode + + +def test_app_detail_preserves_historical_agent_mode_without_tools( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +): + app_obj = _persist_response_graph(sqlite_session) + app_obj.mode = AppMode.AGENT_CHAT + config = sqlite_session.get(AppModelConfig, CONFIG_ID) + assert config is not None + agent_mode = {"enabled": True, "max_iteration": 5, "strategy": "function_call"} + config.agent_mode = json.dumps(agent_mode) + sqlite_session.commit() + monkeypatch.setattr( + "repositories.app.response.load_annotation_reply_config", lambda _session, _app_id: {"enabled": False} + ) + + with app.test_request_context("/"): + response = controller._app_detail_response( + app_record(app_obj, session=sqlite_session, projection="detail-with-site") + ).model_dump(mode="json") + + assert response["model_config"]["agent_mode"] == agent_mode + + +def test_app_model_config_response_round_trips_persisted_dataset_and_external_tool_fields( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +): + app_obj = _persist_response_graph(sqlite_session) + config = sqlite_session.get(AppModelConfig, CONFIG_ID) + assert config is not None + dataset_configs = { + "retrieval_model": "multiple", + "reranking_enable": True, + "reranking_enabled": True, + "metadata_filtering_conditions": { + "logical_operator": "and", + "conditions": [ + { + "id": "condition-1", + "metadata_id": "metadata-1", + "name": "category", + "comparison_operator": "is", + "value": "books", + } + ], + }, + "extension_setting": {"value": 1}, + } + external_form = { + "external_data_tool": { + "variable": "customer", + "label": "Customer", + "type": "api", + "enabled": True, + "icon": "icon-key", + "icon_background": "#fff", + "config": {"provider": "crm"}, + "extension_setting": {"value": 2}, + } + } + config.dataset_configs = json.dumps(dataset_configs) + config.user_input_form = json.dumps([external_form]) + sqlite_session.commit() + monkeypatch.setattr( + "repositories.app.response.load_annotation_reply_config", lambda _session, _app_id: {"enabled": False} + ) + + with app.test_request_context("/"): + response = controller._app_detail_response( + app_record(app_obj, session=sqlite_session, projection="detail-with-site") + ).model_dump(mode="json") + + model_config = response["model_config"] + assert model_config["dataset_configs"] == dataset_configs + assert model_config["user_input_form"] == [external_form] + + reimported = AppModelConfig(app_id=APP_ID) + reimported.from_model_config_dict(model_config) + assert reimported.dataset_configs_dict == dataset_configs + assert reimported.user_input_form_list == [external_form] + + +def test_recommended_app_model_configs_keep_all_published_fields(): + catalog_path = Path(__file__).resolve().parents[5] / "constants" / "recommended_apps.json" + catalog = json.loads(catalog_path.read_text()) + + def assert_fields_preserved(source, serialized): + if isinstance(source, dict): + for key, value in source.items(): + assert key in serialized + assert_fields_preserved(value, serialized[key]) + elif isinstance(source, list): + assert len(source) == len(serialized) + for value, serialized_value in zip(source, serialized, strict=True): + assert_fields_preserved(value, serialized_value) + else: + assert serialized == source + + for entry in catalog["app_details"].values(): + source = yaml.safe_load(entry["export_data"]).get("model_config") or {} + if not source: + continue + payload = {**source, "created_by": None, "created_at": 1, "updated_by": None, "updated_at": 1} + response = AppModelConfigResponse.model_validate(payload).model_dump(mode="json") + assert_fields_preserved(source, response) + + +def test_app_detail_rejects_invalid_known_configuration_fields( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +): + app_obj = _persist_response_graph(sqlite_session) + monkeypatch.setattr( + "repositories.app.response.load_annotation_reply_config", lambda _session, _app_id: {"enabled": False} + ) + with app.test_request_context("/"): + detail = controller._app_detail_response( + app_record(app_obj, session=sqlite_session, projection="detail-with-site") + ).model_dump(mode="json") + + for field in ("mode", "permission_keys", "site", "model_config"): + with pytest.raises(ValidationError): + app_fields.AppDetailWithSite.model_validate({key: value for key, value in detail.items() if key != field}) + + with pytest.raises(ValidationError): + app_fields.AppDetailWithSite.model_validate({**detail, "mode": "unsupported"}) + + config = detail["model_config"] + for invalid in ( + {"speech_to_text": {"enabled": "not-a-boolean"}}, + {"agent_mode": {"enabled": True, "strategy": "unsupported", "tools": []}}, + {"agent_mode": {"enabled": True, "tools": "not-a-list"}}, + {"agent_mode": {"enabled": True, "tools": [{"provider_id": "only-id"}]}}, + {"dataset_configs": {"retrieval_model": "unsupported"}}, + {"dataset_configs": {"retrieval_model": "multiple", "reranking_mode": "unsupported"}}, + {"model": {"provider": "openai", "name": "gpt-4o", "mode": "unsupported"}}, + {"user_input_form": [{"unsupported": {"label": "Name", "variable": "name"}}]}, + ): + with pytest.raises(ValidationError): + app_fields.AppDetailWithSite.model_validate({**detail, "model_config": {**config, **invalid}}) diff --git a/api/tests/unit_tests/controllers/console/app/test_console_app_api.py b/api/tests/unit_tests/controllers/console/app/test_console_app_api.py index a497bcd1fd0..f244d27d4da 100644 --- a/api/tests/unit_tests/controllers/console/app/test_console_app_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_console_app_api.py @@ -3,6 +3,7 @@ import inspect from collections.abc import Callable from dataclasses import dataclass, field, replace +from datetime import datetime from io import BytesIO from typing import cast from uuid import UUID @@ -18,6 +19,7 @@ from controllers.console.app.error import AppNotFoundError from libs.login import AccountWithTenant from machinery.context import RequestContext from models.account import Account, TenantAccountRole +from models.enums import CustomizeTokenStrategy from services.agent.roster_package_entities import RosterAgentPackageExport from services.app.console_service import ( AppExportPaidPlanRequiredError, @@ -41,7 +43,33 @@ from tests.unit_tests.config_override import apply_config_overrides APP_ID = UUID("11111111-1111-1111-1111-111111111111") CONTEXT = RequestContext("request", "trace", "actor", "workspace") -RECORD = AppRecord(str(APP_ID), "Example", "chat") +RECORD = AppRecord( + str(APP_ID), "Example", "chat", description="", created_at=datetime(2024, 1, 1), updated_at=datetime(2024, 1, 1) +) +SITE = { + "code": "site", + "title": "Example", + "icon_type": None, + "icon": None, + "icon_background": None, + "description": None, + "default_language": "en-US", + "chat_color_theme": None, + "chat_color_theme_inverted": False, + "customize_domain": None, + "copyright": None, + "privacy_policy": None, + "input_placeholder": None, + "custom_disclaimer": "", + "customize_token_strategy": CustomizeTokenStrategy.NOT_ALLOW, + "prompt_public": False, + "show_workflow_steps": False, + "use_icon_as_answer_icon": False, + "created_by": None, + "created_at": datetime(2024, 1, 1), + "updated_by": None, + "updated_at": datetime(2024, 1, 1), +} @dataclass @@ -133,7 +161,7 @@ def test_detail_resolves_urls_at_the_http_boundary( SERVICE_API_URL="https://api.example.com" if configured else "", APP_WEB_URL="https://apps.example.com" if configured else "", ) - boundary.record = replace(RECORD, site={"code": "site", "title": "Example"}) + boundary.record = replace(RECORD, site=SITE) with app.test_request_context("/", base_url="https://console.example.com/"): response = cast(dict[str, object], invoke(controller.AppApi, controller.AppApi.get, app_id=APP_ID)) assert response["api_base_url"] == ( diff --git a/api/tests/unit_tests/controllers/console/explore/test_trial_preview_details.py b/api/tests/unit_tests/controllers/console/explore/test_trial_preview_details.py index 60fd3fbb24c..7e0f3297fd1 100644 --- a/api/tests/unit_tests/controllers/console/explore/test_trial_preview_details.py +++ b/api/tests/unit_tests/controllers/console/explore/test_trial_preview_details.py @@ -48,7 +48,7 @@ from repositories.app_preview_query_repository import AppPreviewQueryRepository from repositories.trial_app_repository import TrialAppRepository from services.app_preview_details_adapters import AppPreviewDetailsRuntime from services.app_preview_query_service import AppPreviewQueryService -from services.app_service import AppResponseView, AppService +from services.app_service import AppResponseView from services.recommended_app_query_service import ( RecommendedAppCatalogPage, RecommendedAppDetailRecord, @@ -204,9 +204,8 @@ class _Harness: with self.app.test_request_context(f"/trial-apps/{self.target.id}"), self.factory() as session: app = session.get(App, self.target.id) assert app is not None - app = AppService().get_app(app, session=session) return trial_module.TrialAppDetailResponse.model_validate( - AppResponseView(app, session=session), from_attributes=True + AppResponseView(app, session=session, account=self.viewer), from_attributes=True ).model_dump(mode="json") diff --git a/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py b/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py index 0a0ffe657c9..12a4a86d86d 100644 --- a/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py +++ b/api/tests/unit_tests/core/app/app_config/features/test_additional_feature_managers.py @@ -81,6 +81,10 @@ class TestAdditionalFeatureManagers: SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( {"suggested_questions_after_answer": {"enabled": True, "prompt": 123}} ) + with pytest.raises(ValueError, match="prompt in suggested_questions_after_answer must be of string type"): + SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( + {"suggested_questions_after_answer": {"enabled": False, "prompt": None}} + ) with pytest.raises(ValueError, match="must be less than or equal to 1000 characters"): SuggestedQuestionsAfterAnswerConfigManager.validate_and_set_defaults( {"suggested_questions_after_answer": {"enabled": True, "prompt": "a" * 1001}} @@ -128,6 +132,16 @@ class TestAdditionalFeatureManagers: TextToSpeechConfigManager.validate_and_set_defaults({"text_to_speech": "bad"}) with pytest.raises(ValueError): TextToSpeechConfigManager.validate_and_set_defaults({"text_to_speech": {"enabled": "yes"}}) + for field, value in (("voice", None), ("language", 42), ("autoPlay", "auto"), ("autoPlay", None)): + with pytest.raises(ValueError, match=f"{field} in text_to_speech"): + TextToSpeechConfigManager.validate_and_set_defaults( + {"text_to_speech": {"enabled": False, field: value}} + ) + + valid, _ = TextToSpeechConfigManager.validate_and_set_defaults( + {"text_to_speech": {"enabled": True, "voice": "v", "language": "en", "autoPlay": "enabled"}} + ) + assert valid["text_to_speech"]["autoPlay"] == "enabled" result = TextToSpeechConfigManager.convert( {"text_to_speech": {"enabled": True, "voice": "v", "language": "en"}} diff --git a/api/tests/unit_tests/models/test_app_models.py b/api/tests/unit_tests/models/test_app_models.py index f93aad88991..98f993a006c 100644 --- a/api/tests/unit_tests/models/test_app_models.py +++ b/api/tests/unit_tests/models/test_app_models.py @@ -427,6 +427,18 @@ class TestAppModelValidation: class TestAppModelConfig: """Test suite for AppModelConfig model.""" + def test_legacy_dataset_configs_default_retrieval_model_without_dropping_settings(self): + config = AppModelConfig( + app_id=str(uuid4()), + dataset_configs=json.dumps({"reranking_enable": True, "top_k": 8}), + ) + + assert config.dataset_configs_dict == { + "retrieval_model": "single", + "reranking_enable": True, + "top_k": 8, + } + def test_app_model_config_creation(self): """Test creating an AppModelConfig.""" # Arrange diff --git a/api/tests/unit_tests/repositories/test_console_app_repository.py b/api/tests/unit_tests/repositories/test_console_app_repository.py index 8cbaf54a5c7..e90243dd74c 100644 --- a/api/tests/unit_tests/repositories/test_console_app_repository.py +++ b/api/tests/unit_tests/repositories/test_console_app_repository.py @@ -9,7 +9,7 @@ from flask import has_request_context from sqlalchemy import Engine, event, func, select, text from sqlalchemy.orm import Session, sessionmaker -from fields.app_fields import AppDetailWithSite, AppPagination +from fields.app_fields import AppPagination from machinery.context import RequestContext from models.agent import Agent, AgentScope, AgentSource, AgentStatus from models.enums import CustomizeTokenStrategy @@ -101,13 +101,11 @@ def test_all_single_app_operations_preserve_visibility( assert sqlite_session.scalar(select(func.count()).select_from(AppStar)) == 0 -def test_detail_is_serializable_after_repository_session_closes( +def test_detail_is_materialized_after_repository_session_closes( repository: ConsoleAppRepository, sqlite_session: Session, sqlite_engine: Engine, - config_overrides: Callable[..., None], ) -> None: - config_overrides(SERVICE_API_URL="", APP_WEB_URL="") assert not has_request_context() app = persist_app(sqlite_session) config = AppModelConfig(app_id=app.id, model='{"provider":"openai","name":"example"}', pre_prompt="Prompt") @@ -129,14 +127,16 @@ def test_detail_is_serializable_after_repository_session_closes( event.listen(sqlite_engine, "before_cursor_execute", reject_queries) try: - data = AppDetailWithSite.model_validate(record, from_attributes=True).model_dump(mode="json") + model_config = record.app_model_config + site_data = record.site + description = record.description finally: event.remove(sqlite_engine, "before_cursor_execute", reject_queries) - assert data["model_config"]["model"] == {"provider": "openai", "name": "example"} - assert data["site"]["access_token"] == "site-code" - assert data["description"] == "" - assert data["api_base_url"] is None - assert data["site"]["app_base_url"] is None + assert model_config is not None + assert model_config["model"] == {"provider": "openai", "name": "example"} + assert site_data is not None + assert site_data["code"] == "site-code" + assert description == "" @pytest.mark.parametrize( diff --git a/api/tests/unit_tests/services/app/test_console_service.py b/api/tests/unit_tests/services/app/test_console_service.py index d65164357a0..20b181f197c 100644 --- a/api/tests/unit_tests/services/app/test_console_service.py +++ b/api/tests/unit_tests/services/app/test_console_service.py @@ -17,7 +17,6 @@ from core.plugin.impl.plugin import PluginInstaller from core.tools.entities.tool_entities import ApiProviderSchemaType from core.tools.tool_manager import ToolManager from events.app_event import app_was_updated -from fields.app_fields import AppDetailWithSite from graphon.model_runtime.entities.model_entities import ModelType from machinery.context import RequestContext from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole @@ -839,13 +838,11 @@ def test_detail_tool_enrichment_releases_database_before_plugin_io( assert read.scalar(select(App.id).where(App.name == "Created")) is not None else: result = read_result() - data = AppDetailWithSite.model_validate(result, from_attributes=True).model_dump(mode="json") - assert data["deleted_tools"] == [ + assert result.deleted_tools == [ {"type": "api", "provider_id": missing_api, "tool_name": "tool"}, {"type": "api", "provider_id": providers[1].id, "tool_name": "tool"}, {"type": "builtin", "provider_id": "vendor/missing/tool", "tool_name": "tool"}, ] - assert "tool_references" not in data assert remote_calls == [["vendor/missing/tool", "vendor/installed/tool"]] assert not checked_out finally: diff --git a/api/tests/unit_tests/services/test_app_dsl_service.py b/api/tests/unit_tests/services/test_app_dsl_service.py index 60e10a36b85..544142e9d4b 100644 --- a/api/tests/unit_tests/services/test_app_dsl_service.py +++ b/api/tests/unit_tests/services/test_app_dsl_service.py @@ -515,6 +515,52 @@ def test_create_or_update_app_flushes_new_model_config_before_signal( assert sqlite_session.in_transaction() +@pytest.mark.parametrize( + ("feature", "field", "value"), + [ + ("suggested_questions_after_answer", "prompt", None), + ("suggested_questions_after_answer", "prompt", 123), + ("text_to_speech", "voice", None), + ("text_to_speech", "language", 123), + ("text_to_speech", "autoPlay", "auto"), + ], +) +def test_chat_dsl_import_rejects_invalid_feature_fields( + sqlite_session: Session, feature: str, field: str, value: object +) -> None: + app = _app() + + with pytest.raises(ValueError, match=field): + AppDslService(session=sqlite_session)._create_or_update_app( + app=app, + data={"app": {"mode": AppMode.CHAT}, "model_config": {feature: {"enabled": True, field: value}}}, + account=_account(), + ) + + assert app.app_model_config_id is None + assert list(sqlite_session.scalars(select(AppModelConfig))) == [] + + +def test_chat_dsl_import_preserves_valid_feature_fields(sqlite_session: Session) -> None: + app = _app() + model_config = { + "suggested_questions_after_answer": {"enabled": True, "prompt": "Follow up"}, + "text_to_speech": {"enabled": True, "voice": "alloy", "language": "en", "autoPlay": "disabled"}, + } + + AppDslService(session=sqlite_session)._create_or_update_app( + app=app, + data={"app": {"mode": AppMode.CHAT}, "model_config": model_config}, + account=_account(), + ) + + assert app.app_model_config_id is not None + persisted = sqlite_session.get(AppModelConfig, app.app_model_config_id) + assert persisted is not None + assert persisted.suggested_questions_after_answer_dict == model_config["suggested_questions_after_answer"] + assert persisted.text_to_speech_dict == model_config["text_to_speech"] + + def test_create_or_update_app_removes_imported_workflow_viewport(monkeypatch: pytest.MonkeyPatch) -> None: session = cast(Session, SimpleNamespace(add=Mock(), flush=Mock(), get=Mock())) service = AppDslService(session=session) diff --git a/api/tests/unit_tests/services/test_app_service.py b/api/tests/unit_tests/services/test_app_service.py index 5ba1c505aaf..5e5a0350583 100644 --- a/api/tests/unit_tests/services/test_app_service.py +++ b/api/tests/unit_tests/services/test_app_service.py @@ -9,7 +9,7 @@ from uuid import uuid4 import pytest from sqlalchemy import event, select -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, sessionmaker from enums import DeploymentEdition from graphon.model_runtime.entities.model_entities import ModelType @@ -31,7 +31,7 @@ from models.provider import TenantDefaultModel from models.workflow import Workflow, WorkflowType from repositories.app.console_repository import ConsoleAppRepository from services.agent.errors import AgentAccessNotReadyError, AgentNameConflictError -from services.app_service import AppListParams, AppService, CreateAppParams +from services.app_service import AppListParams, AppResponseView, AppService, CreateAppParams from services.enterprise import rbac_service as enterprise_rbac_service @@ -586,68 +586,40 @@ def test_get_recent_apps_uses_one_tenant_scoped_projection_query(sqlite_session: assert "app_model_configs" not in select_statements[0].lower() -class TestGetApp: - def test_legacy_agent_detection_uses_caller_session(self, unbound_session: Session): - app = App( - mode=AppMode.CHAT, - ) +class TestAppResponseViewAgentConfig: + def test_masking_persistent_agent_config_does_not_change_database( + self, sqlite_session: Session, sqlite_session_factory: sessionmaker[Session] + ): + tenant_id = str(uuid4()) + app = _persist_app(sqlite_session, tenant_id=tenant_id) + app.mode = AppMode.AGENT_CHAT + original_agent_mode = {"enabled": True, "tools": [{"parameters": {"secret": "encrypted-value"}}]} + config = AppModelConfig(app_id=app.id, agent_mode=json.dumps(original_agent_mode)) + sqlite_session.add(config) + sqlite_session.flush() + app.app_model_config_id = config.id + sqlite_session.commit() + account = Account(name="Test Account", email="test@example.com") account._current_tenant = Tenant(name="Test Tenant") - account._current_tenant.id = "tenant-1" + account._current_tenant.id = tenant_id + masked_agent_mode = {"enabled": True, "tools": [{"parameters": {"secret": "[masked]"}}]} with ( - patch.object(App, "is_agent_with_session", return_value=False) as is_agent, - patch.object(App, "app_model_config_with_session") as get_model_config, - patch("services.app_service.current_user", account), + patch("services.app_service.mask_agent_tool_parameters", return_value=masked_agent_mode), + patch.object(sqlite_session, "refresh", side_effect=AssertionError("App refresh is unnecessary")), ): - assert AppService().get_app(app, session=unbound_session) is app + response_config = AppResponseView(app, session=sqlite_session, account=account).app_model_config + assert response_config is not None + assert response_config.agent_mode_dict == masked_agent_mode + assert config.agent_mode_dict == original_agent_mode + assert not sqlite_session.is_modified(config) + sqlite_session.commit() - is_agent.assert_called_once_with(session=unbound_session) - get_model_config.assert_not_called() - - def test_agent_model_config_uses_caller_session(self, unbound_session: Session): - app = App( - mode=AppMode.AGENT_CHAT, - ) - account = Account(name="Test Account", email="test@example.com") - account._current_tenant = Tenant(name="Test Tenant") - account._current_tenant.id = "tenant-1" - - with ( - patch.object(App, "is_agent_with_session") as is_agent, - patch.object(App, "app_model_config_with_session", return_value=None) as get_model_config, - patch("services.app_service.current_user", account), - ): - assert AppService().get_app(app, session=unbound_session) is app - - is_agent.assert_not_called() - get_model_config.assert_called_once_with(session=unbound_session) - - def test_masked_agent_config_is_served_through_the_session_accessor(self, unbound_session: Session): - """The masked config must be reachable via `app_model_config_with_session`. - - Every response path resolves an `App`'s model config through that accessor - (`AppResponseView.app_model_config`), never through a raw attribute, so the - masking `get_app` applies has to be observable there. - """ - app = App(mode=AppMode.AGENT_CHAT) - app.id = str(uuid4()) - masked_config = AppModelConfig(app_id=app.id, agent_mode=json.dumps({"enabled": True, "tools": []})) - # A second, unmasked instance: what a fresh lookup would hand back if the - # returned app ever fell through to the base accessor. - refetched_config = AppModelConfig(app_id=app.id, agent_mode=json.dumps({"enabled": True, "tools": []})) - account = Account(name="Test Account", email="test@example.com") - account._current_tenant = Tenant(name="Test Tenant") - account._current_tenant.id = "tenant-1" - - with ( - patch.object(App, "app_model_config_with_session", side_effect=[masked_config, refetched_config]), - patch("services.app_service.current_user", account), - ): - result = AppService().get_app(app, session=unbound_session) - - assert result is not app - assert result.app_model_config_with_session(session=unbound_session) is masked_config + with sqlite_session_factory() as read_session: + saved_config = read_session.get(AppModelConfig, config.id) + assert saved_config is not None + assert saved_config.agent_mode_dict == original_agent_mode class TestAgentAppType: diff --git a/packages/contracts/generated/api/console/agent/types.gen.ts b/packages/contracts/generated/api/console/agent/types.gen.ts index 9f55a1e04cf..f2ef8cf1bb0 100644 --- a/packages/contracts/generated/api/console/agent/types.gen.ts +++ b/packages/contracts/generated/api/console/agent/types.gen.ts @@ -23,41 +23,41 @@ export type AgentAppCreatePayload = { } export type AgentAppDetailWithSite = { - access_mode?: string | null + access_mode: WebAppAccessMode | null access_ready?: boolean - api_base_url?: string | null + api_base_url: string app_id?: string | null backing_app_id?: string | null bound_agent_id?: string | null - created_at?: number | null - created_by?: string | null + created_at: number + created_by: string | null debug_conversation_has_messages?: boolean debug_conversation_id?: string | null debug_conversation_message_count?: number - deleted_tools?: Array - description?: string | null + deleted_tools: Array + description: string enable_api: boolean enable_site: boolean hidden_app_backed?: boolean - icon?: string | null - icon_background?: string | null - icon_type?: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null readonly icon_url: string | null id: string - maintainer?: string | null - max_active_requests?: number | null - mode: string - model_config?: AppModelConfigResponse | null + maintainer: string | null + max_active_requests: number | null + mode: AppMode + model_config: AppModelConfigResponse | null name: string permission_keys: Array role?: string | null - site?: AppDetailSiteResponse | null - tags?: Array - tracing?: unknown | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null - workflow?: WorkflowPartial | null + site: AppDetailSiteResponse | null + tags: Array + tracing: string | null + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean + workflow: WorkflowPartial | null } export type AgentInviteOptionsResponse = { @@ -443,7 +443,7 @@ export type AgentConfigSnapshotRestoreResponse = { } export type AgentAppPartial = { - access_mode?: string | null + access_mode?: WebAppAccessMode | null active_config_is_published?: boolean app_id?: string | null author_name?: string | null @@ -458,13 +458,13 @@ export type AgentAppPartial = { hidden_app_backed?: boolean icon?: string | null icon_background?: string | null - icon_type?: string | null + icon_type?: IconType | null readonly icon_url: string | null id: string is_starred?: boolean maintainer?: string | null max_active_requests?: number | null - mode: string + mode: AppMode model_config?: ModelConfigPartial | null name: string permission_keys: Array @@ -486,65 +486,89 @@ export type AgentPublicationCountsResponse = { export type IconType = 'emoji' | 'image' | 'link' +export type WebAppAccessMode = 'private' | 'private_all' | 'public' | 'sso_verified' + export type DeletedTool = { provider_id: string tool_name: string type: string } +export type AppMode = + | 'advanced-chat' + | 'agent' + | 'agent-chat' + | 'channel' + | 'chat' + | 'completion' + | 'rag-pipeline' + | 'workflow' + export type AppModelConfigResponse = { - agent_mode?: unknown | null - annotation_reply?: unknown | null - chat_prompt_config?: unknown | null - completion_prompt_config?: unknown | null - created_at?: number | null - created_by?: string | null - dataset_configs?: unknown | null - dataset_query_variable?: string | null - external_data_tools?: unknown | null - file_upload?: unknown | null - model?: unknown | null - more_like_this?: unknown | null - opening_statement?: string | null - pre_prompt?: string | null - prompt_type?: string | null - retriever_resource?: unknown | null - sensitive_word_avoidance?: unknown | null - speech_to_text?: unknown | null - suggested_questions?: unknown | null - suggested_questions_after_answer?: unknown | null - text_to_speech?: unknown | null - updated_at?: number | null - updated_by?: string | null - user_input_form?: unknown | null + agent_mode: AppAgentModeResponse + annotation_reply: AppAnnotationReplyEnabledResponse | AppAnnotationReplyDisabledResponse + chat_prompt_config: AppChatPromptConfigResponse + completion_prompt_config: AppCompletionPromptConfigResponse + created_at: number + created_by: string | null + dataset_configs: AppDatasetConfigsResponse + dataset_query_variable: string | null + external_data_tools: Array< + AppEnabledExternalDataToolResponse | AppDisabledExternalDataToolResponse + > + file_upload: AppFileUploadResponse + model: AppModelSelectionResponse + more_like_this: AppEnabledConfigResponse + opening_statement: string | null + pre_prompt: string | null + prompt_type: PromptType + retriever_resource: AppEnabledConfigResponse + sensitive_word_avoidance: AppSensitiveWordAvoidanceResponse + speech_to_text: AppEnabledConfigResponse + suggested_questions: Array + suggested_questions_after_answer: AppSuggestedQuestionsAfterAnswerResponse + text_to_speech: AppTextToSpeechResponse + updated_at: number + updated_by: string | null + user_input_form: Array< + | AppTextInputFormResponse + | AppSelectFormResponse + | AppParagraphFormResponse + | AppNumberFormResponse + | AppCheckboxFormResponse + | AppFileFormResponse + | AppFileListFormResponse + | AppExternalDataToolFormResponse + | AppJsonObjectFormResponse + > } export type AppDetailSiteResponse = { - access_token?: string | null - app_base_url?: string | null - chat_color_theme?: string | null - chat_color_theme_inverted?: boolean | null - code?: string | null - copyright?: string | null - created_at?: number | null - created_by?: string | null - custom_disclaimer?: string | null - customize_domain?: string | null - customize_token_strategy?: string | null - default_language?: string | null - description?: string | null - icon?: string | null - icon_background?: string | null - icon_type?: string | IconType | null + access_token: string | null + app_base_url: string + chat_color_theme: string | null + chat_color_theme_inverted: boolean + code: string | null + copyright: string | null + created_at: number + created_by: string | null + custom_disclaimer: string + customize_domain: string | null + customize_token_strategy: CustomizeTokenStrategy + default_language: string + description: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null readonly icon_url: string | null - input_placeholder?: string | null - privacy_policy?: string | null - prompt_public?: boolean | null - show_workflow_steps?: boolean | null - title?: string | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null + input_placeholder: string | null + privacy_policy: string | null + prompt_public: boolean + show_workflow_steps: boolean + title: string + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean } export type Tag = { @@ -794,7 +818,7 @@ export type AgentSuggestedQuestionsAfterAnswerFeatureConfig = { } export type AgentTextToSpeechFeatureConfig = { - autoPlay?: string | null + autoPlay?: 'disabled' | 'enabled' | null enabled?: boolean language?: string | null voice?: string | null @@ -989,7 +1013,7 @@ export type AgentConfigRevisionResponse = { export type ModelConfigPartial = { created_at?: number | null created_by?: string | null - model?: unknown | null + model?: AppModelSelectionResponse | null pre_prompt?: string | null updated_at?: number | null updated_by?: string | null @@ -1003,6 +1027,172 @@ export type AgentAppPublishedReferenceResponse = { app_name: string } +export type AppAgentModeResponse = { + enabled: boolean + max_iteration?: number + prompt?: AppAgentPromptResponse | string | null + strategy?: PlanningStrategy | 'cot' | 'function-calling' | null + tools?: Array< + | AppProviderAgentToolResponse + | AppLegacyDatasetToolResponse + | AppLegacyGoogleSearchToolResponse + | AppLegacyWebReaderToolResponse + | AppLegacyWikipediaToolResponse + | AppLegacyCurrentDatetimeToolResponse + | AppLegacySensitiveWordToolResponseItem + > + [key: string]: unknown +} + +export type AppAnnotationReplyEnabledResponse = { + embedding_model: AppEmbeddingModelResponse + enabled: true + id: string + score_threshold: number +} + +export type AppAnnotationReplyDisabledResponse = { + enabled: false +} + +export type AppChatPromptConfigResponse = { + prompt?: Array +} + +export type AppCompletionPromptConfigResponse = { + conversation_histories_role?: AppConversationHistoriesRoleResponse + prompt?: AppCompletionPromptTextResponse +} + +export type AppDatasetConfigsResponse = { + datasets?: AppDatasetListResponse + metadata_filtering_conditions?: AppMetadataFilteringConditionsResponse | null + metadata_filtering_mode?: 'automatic' | 'disabled' | 'manual' + metadata_model_config?: AppModelSelectionResponse | null + reranking_enable?: boolean + reranking_enabled?: boolean + reranking_mode?: RerankMode + reranking_model?: AppRerankingModelResponse | null + retrieval_model: 'multiple' | 'single' + score_threshold?: number | null + score_threshold_enabled?: boolean + top_k?: number + weights?: AppWeightsResponse | null + [key: string]: unknown +} + +export type AppEnabledExternalDataToolResponse = { + config: { + [key: string]: JsonValue2 + } + enabled: true + icon?: string + icon_background?: string + label?: string + type: string + variable: string + [key: string]: unknown +} + +export type AppDisabledExternalDataToolResponse = { + config?: { + [key: string]: JsonValue2 + } + enabled: false + icon?: string + icon_background?: string + label?: string + type?: string + variable?: string + [key: string]: unknown +} + +export type AppFileUploadResponse = { + allowed_file_extensions?: Array + allowed_file_types?: Array + allowed_file_upload_methods?: Array + enabled?: boolean + image?: AppImageUploadResponse + number_limits?: number +} + +export type AppModelSelectionResponse = { + completion_params?: { + [key: string]: JsonValue2 + } + mode?: LlmMode | '' + name?: string + provider?: string + [key: string]: unknown +} + +export type AppEnabledConfigResponse = { + enabled: boolean +} + +export type PromptType = 'advanced' | 'simple' + +export type AppSensitiveWordAvoidanceResponse = { + config?: { + [key: string]: JsonValue2 + } + configs?: Array + enabled: boolean + type?: string + [key: string]: unknown +} + +export type AppSuggestedQuestionsAfterAnswerResponse = { + enabled: boolean + model?: AppModelSelectionResponse + prompt?: string +} + +export type AppTextToSpeechResponse = { + autoPlay?: 'disabled' | 'enabled' + enabled: boolean + language?: string + voice?: string +} + +export type AppTextInputFormResponse = { + 'text-input': AppUserInputFormConfigResponse +} + +export type AppSelectFormResponse = { + select: AppUserInputFormConfigResponse +} + +export type AppParagraphFormResponse = { + paragraph: AppUserInputFormConfigResponse +} + +export type AppNumberFormResponse = { + number: AppUserInputFormConfigResponse +} + +export type AppCheckboxFormResponse = { + checkbox: AppUserInputFormConfigResponse +} + +export type AppFileFormResponse = { + file: AppUserInputFormConfigResponse +} + +export type AppFileListFormResponse = { + 'file-list': AppUserInputFormConfigResponse +} + +export type AppExternalDataToolFormResponse = { + external_data_tool: AppUserInputFormConfigResponse +} + +export type AppJsonObjectFormResponse = { + json_object: AppUserInputFormConfigResponse +} + +export type CustomizeTokenStrategy = 'allow' | 'must' | 'not_allow' | 'uuid' + export type AgentKind = 'dify_agent' export type AgentPublishedReferenceResponse = { @@ -1341,6 +1531,158 @@ export type AgentConfigRevisionOperation = | 'save_new_version' | 'save_to_roster' +export type AppAgentPromptResponse = { + first_prompt?: string + next_iteration?: string + [key: string]: unknown +} + +export type PlanningStrategy = 'function_call' | 'react' | 'react_router' | 'router' + +export type AppProviderAgentToolResponse = { + credential_id?: string | null + enabled?: boolean + isDeleted?: boolean + notAuthor?: boolean + plugin_unique_identifier?: string | null + provider_id: string + provider_name?: string + provider_type: ToolProviderType + tool_label?: string + tool_name: string + tool_parameters: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyDatasetToolResponse = { + dataset: AppDatasetReferenceResponse + [key: string]: unknown +} + +export type AppLegacyGoogleSearchToolResponse = { + google_search: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyWebReaderToolResponse = { + web_reader: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyWikipediaToolResponse = { + wikipedia: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyCurrentDatetimeToolResponse = { + current_datetime: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacySensitiveWordToolResponseItem = { + 'sensitive-word-avoidance': AppLegacySensitiveWordToolResponse + [key: string]: unknown +} + +export type AppEmbeddingModelResponse = { + embedding_model_name: string + embedding_provider_name: string +} + +export type AppChatPromptMessageResponse = { + role: string + text: string +} + +export type AppConversationHistoriesRoleResponse = { + assistant_prefix: string + user_prefix: string +} + +export type AppCompletionPromptTextResponse = { + text: string +} + +export type AppDatasetListResponse = { + datasets: Array + strategy?: string + [key: string]: unknown +} + +export type AppMetadataFilteringConditionsResponse = { + conditions?: Array | null + logical_operator?: 'and' | 'or' | null + [key: string]: unknown +} + +export type RerankMode = 'reranking_model' | 'weighted_score' + +export type AppRerankingModelResponse = { + reranking_model_name?: string + reranking_provider_name?: string + [key: string]: unknown +} + +export type AppWeightsResponse = { + keyword_setting: AppKeywordSettingResponse + vector_setting: AppVectorSettingResponse + weight_type?: 'customized' | 'keyword_first' | 'semantic_first' + [key: string]: unknown +} + +export type JsonValue2 = unknown + +export type FileType = 'audio' | 'custom' | 'document' | 'image' | 'video' + +export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url' | 'tool_file' + +export type AppImageUploadResponse = { + detail?: 'high' | 'low' | null + enabled?: boolean + number_limits?: number + transfer_methods?: Array +} + +export type LlmMode = 'chat' | 'completion' + +export type AppUserInputFormConfigResponse = { + allowed_file_extensions?: Array + allowed_file_types?: Array + allowed_file_upload_methods?: Array + config?: { + [key: string]: JsonValue2 + } + default?: JsonValue2 + description?: string + enabled?: boolean + hide?: boolean + icon?: string | null + icon_background?: string | null + json_schema?: + | string + | { + [key: string]: JsonValue2 + } + | null + label: string + max_length?: number | null + options?: Array + required?: boolean + type?: string + variable: string + [key: string]: unknown +} + export type AgentFileUploadFeatureConfig = { allowed_file_extensions?: Array allowed_file_types?: Array @@ -1592,11 +1934,71 @@ export type FormInputConfig = type: 'file-list' } & FileListInputConfig) -export type JsonValue2 = unknown +export type ToolProviderType = + | 'api' + | 'app' + | 'builtin' + | 'dataset-retrieval' + | 'mcp' + | 'plugin' + | 'workflow' -export type FileType = 'audio' | 'custom' | 'document' | 'image' | 'video' +export type AppDatasetReferenceResponse = { + enabled?: boolean + id?: string + [key: string]: unknown +} -export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url' | 'tool_file' +export type AppLegacySensitiveWordToolResponse = { + canned_response: string + enabled: boolean + words: Array + [key: string]: unknown +} + +export type AppDatasetItemResponse = { + dataset: AppDatasetReferenceResponse + [key: string]: unknown +} + +export type AppMetadataConditionResponse = { + comparison_operator: + | '<' + | '=' + | '>' + | 'after' + | 'before' + | 'contains' + | 'empty' + | 'end with' + | 'in' + | 'is' + | 'is not' + | 'not contains' + | 'not empty' + | 'not in' + | 'start with' + | '≠' + | '≤' + | '≥' + id?: string + metadata_id?: string + name: string + value?: string | Array | number | number | null + [key: string]: unknown +} + +export type AppKeywordSettingResponse = { + keyword_weight: number + [key: string]: unknown +} + +export type AppVectorSettingResponse = { + embedding_model_name: string + embedding_provider_name: string + vector_weight: number + [key: string]: unknown +} export type AgentFileUploadImageFeatureConfig = { enabled?: boolean @@ -1642,15 +2044,6 @@ export type AgentSoulDifyToolCredentialRef = { type?: 'provider' | 'tool' } -export type ToolProviderType = - | 'api' - | 'app' - | 'builtin' - | 'dataset-retrieval' - | 'mcp' - | 'plugin' - | 'workflow' - export type OutputErrorStrategy = 'default_value' | 'fail_branch' | 'stop' export type DeclaredOutputRetryConfig = { @@ -1772,44 +2165,44 @@ export type AgentAppPaginationWritable = { } export type AgentAppDetailWithSiteWritable = { - access_mode?: string | null + access_mode: WebAppAccessMode | null access_ready?: boolean - api_base_url?: string | null + api_base_url: string app_id?: string | null backing_app_id?: string | null bound_agent_id?: string | null - created_at?: number | null - created_by?: string | null + created_at: number + created_by: string | null debug_conversation_has_messages?: boolean debug_conversation_id?: string | null debug_conversation_message_count?: number - deleted_tools?: Array - description?: string | null + deleted_tools: Array + description: string enable_api: boolean enable_site: boolean hidden_app_backed?: boolean - icon?: string | null - icon_background?: string | null - icon_type?: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null id: string - maintainer?: string | null - max_active_requests?: number | null - mode: string - model_config?: AppModelConfigResponse | null + maintainer: string | null + max_active_requests: number | null + mode: AppMode + model_config: AppModelConfigResponse | null name: string permission_keys: Array role?: string | null - site?: AppDetailSiteResponseWritable | null - tags?: Array - tracing?: unknown | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null - workflow?: WorkflowPartial | null + site: AppDetailSiteResponseWritable | null + tags: Array + tracing: string | null + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean + workflow: WorkflowPartial | null } export type AgentAppPartialWritable = { - access_mode?: string | null + access_mode?: WebAppAccessMode | null active_config_is_published?: boolean app_id?: string | null author_name?: string | null @@ -1824,12 +2217,12 @@ export type AgentAppPartialWritable = { hidden_app_backed?: boolean icon?: string | null icon_background?: string | null - icon_type?: string | null + icon_type?: IconType | null id: string is_starred?: boolean maintainer?: string | null max_active_requests?: number | null - mode: string + mode: AppMode model_config?: ModelConfigPartial | null name: string permission_keys: Array @@ -1845,30 +2238,30 @@ export type AgentAppPartialWritable = { } export type AppDetailSiteResponseWritable = { - access_token?: string | null - app_base_url?: string | null - chat_color_theme?: string | null - chat_color_theme_inverted?: boolean | null - code?: string | null - copyright?: string | null - created_at?: number | null - created_by?: string | null - custom_disclaimer?: string | null - customize_domain?: string | null - customize_token_strategy?: string | null - default_language?: string | null - description?: string | null - icon?: string | null - icon_background?: string | null - icon_type?: string | IconType | null - input_placeholder?: string | null - privacy_policy?: string | null - prompt_public?: boolean | null - show_workflow_steps?: boolean | null - title?: string | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null + access_token: string | null + app_base_url: string + chat_color_theme: string | null + chat_color_theme_inverted: boolean + code: string | null + copyright: string | null + created_at: number + created_by: string | null + custom_disclaimer: string + customize_domain: string | null + customize_token_strategy: CustomizeTokenStrategy + default_language: string + description: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null + input_placeholder: string | null + privacy_policy: string | null + prompt_public: boolean + show_workflow_steps: boolean + title: string + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean } export type GetAgentData = { diff --git a/packages/contracts/generated/api/console/agent/zod.gen.ts b/packages/contracts/generated/api/console/agent/zod.gen.ts index 4377a36429e..a596469f1f5 100644 --- a/packages/contracts/generated/api/console/agent/zod.gen.ts +++ b/packages/contracts/generated/api/console/agent/zod.gen.ts @@ -266,6 +266,11 @@ export const zAgentAppCopyPayload = z.object({ role: z.string().max(255).nullish(), }) +/** + * WebAppAccessMode + */ +export const zWebAppAccessMode = z.enum(['private', 'private_all', 'public', 'sso_verified']) + /** * DeletedTool */ @@ -276,65 +281,18 @@ export const zDeletedTool = z.object({ }) /** - * AppModelConfigResponse + * AppMode */ -export const zAppModelConfigResponse = z.object({ - agent_mode: z.unknown().nullish(), - annotation_reply: z.unknown().nullish(), - chat_prompt_config: z.unknown().nullish(), - completion_prompt_config: z.unknown().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - dataset_configs: z.unknown().nullish(), - dataset_query_variable: z.string().nullish(), - external_data_tools: z.unknown().nullish(), - file_upload: z.unknown().nullish(), - model: z.unknown().nullish(), - more_like_this: z.unknown().nullish(), - opening_statement: z.string().nullish(), - pre_prompt: z.string().nullish(), - prompt_type: z.string().nullish(), - retriever_resource: z.unknown().nullish(), - sensitive_word_avoidance: z.unknown().nullish(), - speech_to_text: z.unknown().nullish(), - suggested_questions: z.unknown().nullish(), - suggested_questions_after_answer: z.unknown().nullish(), - text_to_speech: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - user_input_form: z.unknown().nullish(), -}) - -/** - * AppDetailSiteResponse - */ -export const zAppDetailSiteResponse = z.object({ - access_token: z.string().nullish(), - app_base_url: z.string().nullish(), - chat_color_theme: z.string().nullish(), - chat_color_theme_inverted: z.boolean().nullish(), - code: z.string().nullish(), - copyright: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - custom_disclaimer: z.string().nullish(), - customize_domain: z.string().nullish(), - customize_token_strategy: z.string().nullish(), - default_language: z.string().nullish(), - description: z.string().nullish(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.union([z.string(), zIconType]).nullish(), - icon_url: z.string().nullable(), - input_placeholder: z.string().nullish(), - privacy_policy: z.string().nullish(), - prompt_public: z.boolean().nullish(), - show_workflow_steps: z.boolean().nullish(), - title: z.string().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), -}) +export const zAppMode = z.enum([ + 'advanced-chat', + 'agent', + 'agent-chat', + 'channel', + 'chat', + 'completion', + 'rag-pipeline', + 'workflow', +]) /** * Tag @@ -356,47 +314,6 @@ export const zWorkflowPartial = z.object({ updated_by: z.string().nullish(), }) -/** - * AgentAppDetailWithSite - */ -export const zAgentAppDetailWithSite = z.object({ - access_mode: z.string().nullish(), - access_ready: z.boolean().optional().default(false), - api_base_url: z.string().nullish(), - app_id: z.string().nullish(), - backing_app_id: z.string().nullish(), - bound_agent_id: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - debug_conversation_has_messages: z.boolean().optional().default(false), - debug_conversation_id: z.string().nullish(), - debug_conversation_message_count: z.int().optional().default(0), - deleted_tools: z.array(zDeletedTool).optional(), - description: z.string().nullish(), - enable_api: z.boolean(), - enable_site: z.boolean(), - hidden_app_backed: z.boolean().optional().default(false), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), - icon_url: z.string().nullable(), - id: z.string(), - maintainer: z.string().nullish(), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zAppModelConfigResponse.nullish(), - name: z.string(), - permission_keys: z.array(z.string()), - role: z.string().nullish(), - site: zAppDetailSiteResponse.nullish(), - tags: z.array(zTag).optional(), - tracing: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), -}) - /** * ComposerBindingPayload */ @@ -649,7 +566,7 @@ export const zAgentFeatureToggleConfig = z.object({ * AgentTextToSpeechFeatureConfig */ export const zAgentTextToSpeechFeatureConfig = z.object({ - autoPlay: z.string().nullish(), + autoPlay: z.enum(['disabled', 'enabled']).nullish(), enabled: z.boolean().optional().default(false), language: z.string().nullish(), voice: z.string().nullish(), @@ -834,18 +751,6 @@ export const zTextToSpeechVoiceResponse = z.object({ */ export const zTextToSpeechVoiceListResponse = z.array(zTextToSpeechVoiceResponse) -/** - * ModelConfigPartial - */ -export const zModelConfigPartial = z.object({ - created_at: z.int().nullish(), - created_by: z.string().nullish(), - model: z.unknown().nullish(), - pre_prompt: z.string().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), -}) - /** * AgentAppPublishedReferenceResponse */ @@ -858,55 +763,72 @@ export const zAgentAppPublishedReferenceResponse = z.object({ }) /** - * AgentAppPartial + * AppAnnotationReplyDisabledResponse */ -export const zAgentAppPartial = z.object({ - access_mode: z.string().nullish(), - active_config_is_published: z.boolean().optional().default(false), - app_id: z.string().nullish(), - author_name: z.string().nullish(), - backing_app_id: z.string().nullish(), - bound_agent_id: z.string().nullish(), - create_user_name: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - debug_conversation_id: z.string().nullish(), - description: z.string().nullish(), - has_draft_trigger: z.boolean().nullish(), - hidden_app_backed: z.boolean().optional().default(false), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), - icon_url: z.string().nullable(), - id: z.string(), - is_starred: z.boolean().optional().default(false), - maintainer: z.string().nullish(), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zModelConfigPartial.nullish(), - name: z.string(), - permission_keys: z.array(z.string()), - published_reference_count: z.int().optional().default(0), - published_references: z.array(zAgentAppPublishedReferenceResponse).optional(), - reference_count: z.int().nullish(), - role: z.string().nullish(), - tags: z.array(zTag).optional(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), +export const zAppAnnotationReplyDisabledResponse = z.object({ + enabled: z.literal(false), }) /** - * AgentAppPagination + * AppEnabledConfigResponse */ -export const zAgentAppPagination = z.object({ - data: z.array(zAgentAppPartial), - has_more: z.boolean(), - limit: z.int(), - page: z.int(), - publication_counts: zAgentPublicationCountsResponse, - total: z.int(), +export const zAppEnabledConfigResponse = z.object({ + enabled: z.boolean(), +}) + +/** + * PromptType + * + * Prompt configuration type + */ +export const zPromptType = z.enum(['advanced', 'simple']) + +/** + * AppTextToSpeechResponse + */ +export const zAppTextToSpeechResponse = z.object({ + autoPlay: z.enum(['disabled', 'enabled']).optional(), + enabled: z.boolean(), + language: z.string().optional(), + voice: z.string().optional(), +}) + +/** + * CustomizeTokenStrategy + * + * Site token customization strategy + */ +export const zCustomizeTokenStrategy = z.enum(['allow', 'must', 'not_allow', 'uuid']) + +/** + * AppDetailSiteResponse + */ +export const zAppDetailSiteResponse = z.object({ + access_token: z.string().nullable(), + app_base_url: z.string(), + chat_color_theme: z.string().nullable(), + chat_color_theme_inverted: z.boolean(), + code: z.string().nullable(), + copyright: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + custom_disclaimer: z.string(), + customize_domain: z.string().nullable(), + customize_token_strategy: zCustomizeTokenStrategy, + default_language: z.string(), + description: z.string().nullable(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), + icon_url: z.string().nullable(), + input_placeholder: z.string().nullable(), + privacy_policy: z.string().nullable(), + prompt_public: z.boolean(), + show_workflow_steps: z.boolean(), + title: z.string(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), }) /** @@ -1412,6 +1334,379 @@ export const zAgentConfigRevisionResponse = z.object({ version_note: z.string().nullish(), }) +/** + * AppAgentPromptResponse + */ +export const zAppAgentPromptResponse = z.object({ + first_prompt: z.string().optional(), + next_iteration: z.string().optional(), +}) + +/** + * PlanningStrategy + */ +export const zPlanningStrategy = z.enum(['function_call', 'react', 'react_router', 'router']) + +/** + * AppEmbeddingModelResponse + */ +export const zAppEmbeddingModelResponse = z.object({ + embedding_model_name: z.string(), + embedding_provider_name: z.string(), +}) + +/** + * AppAnnotationReplyEnabledResponse + */ +export const zAppAnnotationReplyEnabledResponse = z.object({ + embedding_model: zAppEmbeddingModelResponse, + enabled: z.literal(true), + id: z.string(), + score_threshold: z.number(), +}) + +/** + * AppChatPromptMessageResponse + */ +export const zAppChatPromptMessageResponse = z.object({ + role: z.string(), + text: z.string(), +}) + +/** + * AppChatPromptConfigResponse + */ +export const zAppChatPromptConfigResponse = z.object({ + prompt: z.array(zAppChatPromptMessageResponse).optional(), +}) + +/** + * AppConversationHistoriesRoleResponse + */ +export const zAppConversationHistoriesRoleResponse = z.object({ + assistant_prefix: z.string(), + user_prefix: z.string(), +}) + +/** + * AppCompletionPromptTextResponse + */ +export const zAppCompletionPromptTextResponse = z.object({ + text: z.string(), +}) + +/** + * AppCompletionPromptConfigResponse + */ +export const zAppCompletionPromptConfigResponse = z.object({ + conversation_histories_role: zAppConversationHistoriesRoleResponse.optional(), + prompt: zAppCompletionPromptTextResponse.optional(), +}) + +/** + * RerankMode + */ +export const zRerankMode = z.enum(['reranking_model', 'weighted_score']) + +/** + * AppRerankingModelResponse + */ +export const zAppRerankingModelResponse = z.object({ + reranking_model_name: z.string().optional(), + reranking_provider_name: z.string().optional(), +}) + +export const zJsonValue2 = z.unknown() + +/** + * AppEnabledExternalDataToolResponse + */ +export const zAppEnabledExternalDataToolResponse = z.object({ + config: z.record(z.string(), zJsonValue2), + enabled: z.literal(true), + icon: z.string().optional(), + icon_background: z.string().optional(), + label: z.string().optional(), + type: z.string(), + variable: z.string(), +}) + +/** + * AppDisabledExternalDataToolResponse + */ +export const zAppDisabledExternalDataToolResponse = z.object({ + config: z.record(z.string(), zJsonValue2).optional(), + enabled: z.literal(false), + icon: z.string().optional(), + icon_background: z.string().optional(), + label: z.string().optional(), + type: z.string().optional(), + variable: z.string().optional(), +}) + +/** + * AppSensitiveWordAvoidanceResponse + */ +export const zAppSensitiveWordAvoidanceResponse = z.object({ + config: z.record(z.string(), zJsonValue2).optional(), + configs: z.array(zJsonValue2).optional(), + enabled: z.boolean(), + type: z.string().optional(), +}) + +/** + * HumanInputFormSubmissionData + */ +export const zHumanInputFormSubmissionData = z.object({ + action_id: z.string(), + action_text: z.string(), + node_id: z.string(), + node_title: z.string(), + rendered_content: z.string(), + submitted_data: z.record(z.string(), zJsonValue2).nullish(), +}) + +/** + * AppLegacyGoogleSearchToolResponse + */ +export const zAppLegacyGoogleSearchToolResponse = z.object({ + google_search: z.record(z.string(), zJsonValue2), +}) + +/** + * AppLegacyWebReaderToolResponse + */ +export const zAppLegacyWebReaderToolResponse = z.object({ + web_reader: z.record(z.string(), zJsonValue2), +}) + +/** + * AppLegacyWikipediaToolResponse + */ +export const zAppLegacyWikipediaToolResponse = z.object({ + wikipedia: z.record(z.string(), zJsonValue2), +}) + +/** + * AppLegacyCurrentDatetimeToolResponse + */ +export const zAppLegacyCurrentDatetimeToolResponse = z.object({ + current_datetime: z.record(z.string(), zJsonValue2), +}) + +/** + * FileType + */ +export const zFileType = z.enum(['audio', 'custom', 'document', 'image', 'video']) + +/** + * FileTransferMethod + */ +export const zFileTransferMethod = z.enum([ + 'datasource_file', + 'local_file', + 'remote_url', + 'tool_file', +]) + +/** + * AppImageUploadResponse + */ +export const zAppImageUploadResponse = z.object({ + detail: z.enum(['high', 'low']).nullish(), + enabled: z.boolean().optional(), + number_limits: z.int().optional(), + transfer_methods: z.array(zFileTransferMethod).optional(), +}) + +/** + * AppFileUploadResponse + */ +export const zAppFileUploadResponse = z.object({ + allowed_file_extensions: z.array(z.string()).optional(), + allowed_file_types: z.array(zFileType).optional(), + allowed_file_upload_methods: z.array(zFileTransferMethod).optional(), + enabled: z.boolean().optional(), + image: zAppImageUploadResponse.optional(), + number_limits: z.int().optional(), +}) + +/** + * LLMMode + * + * Enum class for large language model mode. + */ +export const zLlmMode = z.enum(['chat', 'completion']) + +/** + * AppModelSelectionResponse + */ +export const zAppModelSelectionResponse = z.object({ + completion_params: z.record(z.string(), zJsonValue2).optional(), + mode: z.union([zLlmMode, z.literal('')]).optional(), + name: z.string().optional(), + provider: z.string().optional(), +}) + +/** + * ModelConfigPartial + */ +export const zModelConfigPartial = z.object({ + created_at: z.int().nullish(), + created_by: z.string().nullish(), + model: zAppModelSelectionResponse.nullish(), + pre_prompt: z.string().nullish(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), +}) + +/** + * AgentAppPartial + */ +export const zAgentAppPartial = z.object({ + access_mode: zWebAppAccessMode.nullish(), + active_config_is_published: z.boolean().optional().default(false), + app_id: z.string().nullish(), + author_name: z.string().nullish(), + backing_app_id: z.string().nullish(), + bound_agent_id: z.string().nullish(), + create_user_name: z.string().nullish(), + created_at: z.int().nullish(), + created_by: z.string().nullish(), + debug_conversation_id: z.string().nullish(), + description: z.string().nullish(), + has_draft_trigger: z.boolean().nullish(), + hidden_app_backed: z.boolean().optional().default(false), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + icon_type: zIconType.nullish(), + icon_url: z.string().nullable(), + id: z.string(), + is_starred: z.boolean().optional().default(false), + maintainer: z.string().nullish(), + max_active_requests: z.int().nullish(), + mode: zAppMode, + model_config: zModelConfigPartial.nullish(), + name: z.string(), + permission_keys: z.array(z.string()), + published_reference_count: z.int().optional().default(0), + published_references: z.array(zAgentAppPublishedReferenceResponse).optional(), + reference_count: z.int().nullish(), + role: z.string().nullish(), + tags: z.array(zTag).optional(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), + use_icon_as_answer_icon: z.boolean().nullish(), + workflow: zWorkflowPartial.nullish(), +}) + +/** + * AgentAppPagination + */ +export const zAgentAppPagination = z.object({ + data: z.array(zAgentAppPartial), + has_more: z.boolean(), + limit: z.int(), + page: z.int(), + publication_counts: zAgentPublicationCountsResponse, + total: z.int(), +}) + +/** + * AppSuggestedQuestionsAfterAnswerResponse + */ +export const zAppSuggestedQuestionsAfterAnswerResponse = z.object({ + enabled: z.boolean(), + model: zAppModelSelectionResponse.optional(), + prompt: z.string().optional(), +}) + +/** + * AppUserInputFormConfigResponse + */ +export const zAppUserInputFormConfigResponse = z.object({ + allowed_file_extensions: z.array(z.string()).optional(), + allowed_file_types: z.array(zFileType).optional(), + allowed_file_upload_methods: z.array(zFileTransferMethod).optional(), + config: z.record(z.string(), zJsonValue2).optional(), + default: zJsonValue2.optional(), + description: z.string().optional(), + enabled: z.boolean().optional(), + hide: z.boolean().optional(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + json_schema: z.union([z.string(), z.record(z.string(), zJsonValue2)]).nullish(), + label: z.string(), + max_length: z.int().nullish(), + options: z.array(z.string()).optional(), + required: z.boolean().optional(), + type: z.string().optional(), + variable: z.string(), +}) + +/** + * AppTextInputFormResponse + */ +export const zAppTextInputFormResponse = z.object({ + 'text-input': zAppUserInputFormConfigResponse, +}) + +/** + * AppSelectFormResponse + */ +export const zAppSelectFormResponse = z.object({ + select: zAppUserInputFormConfigResponse, +}) + +/** + * AppParagraphFormResponse + */ +export const zAppParagraphFormResponse = z.object({ + paragraph: zAppUserInputFormConfigResponse, +}) + +/** + * AppNumberFormResponse + */ +export const zAppNumberFormResponse = z.object({ + number: zAppUserInputFormConfigResponse, +}) + +/** + * AppCheckboxFormResponse + */ +export const zAppCheckboxFormResponse = z.object({ + checkbox: zAppUserInputFormConfigResponse, +}) + +/** + * AppFileFormResponse + */ +export const zAppFileFormResponse = z.object({ + file: zAppUserInputFormConfigResponse, +}) + +/** + * AppFileListFormResponse + */ +export const zAppFileListFormResponse = z.object({ + 'file-list': zAppUserInputFormConfigResponse, +}) + +/** + * AppExternalDataToolFormResponse + */ +export const zAppExternalDataToolFormResponse = z.object({ + external_data_tool: zAppUserInputFormConfigResponse, +}) + +/** + * AppJsonObjectFormResponse + */ +export const zAppJsonObjectFormResponse = z.object({ + json_object: zAppUserInputFormConfigResponse, +}) + /** * AgentEnvVariableConfig */ @@ -1790,34 +2085,276 @@ export const zAgentAppFeaturesPayload = z.object({ text_to_speech: zAgentTextToSpeechFeatureConfig.nullish(), }) -export const zJsonValue2 = z.unknown() +/** + * ToolProviderType + * + * Enum class for tool provider + */ +export const zToolProviderType = z.enum([ + 'api', + 'app', + 'builtin', + 'dataset-retrieval', + 'mcp', + 'plugin', + 'workflow', +]) /** - * HumanInputFormSubmissionData + * AppProviderAgentToolResponse */ -export const zHumanInputFormSubmissionData = z.object({ - action_id: z.string(), - action_text: z.string(), - node_id: z.string(), - node_title: z.string(), - rendered_content: z.string(), - submitted_data: z.record(z.string(), zJsonValue2).nullish(), +export const zAppProviderAgentToolResponse = z.object({ + credential_id: z.string().nullish(), + enabled: z.boolean().optional(), + isDeleted: z.boolean().optional(), + notAuthor: z.boolean().optional(), + plugin_unique_identifier: z.string().nullish(), + provider_id: z.string(), + provider_name: z.string().optional(), + provider_type: zToolProviderType, + tool_label: z.string().optional(), + tool_name: z.string(), + tool_parameters: z.record(z.string(), zJsonValue2), }) /** - * FileType + * AppDatasetReferenceResponse */ -export const zFileType = z.enum(['audio', 'custom', 'document', 'image', 'video']) +export const zAppDatasetReferenceResponse = z.object({ + enabled: z.boolean().optional(), + id: z.string().optional(), +}) /** - * FileTransferMethod + * AppLegacyDatasetToolResponse */ -export const zFileTransferMethod = z.enum([ - 'datasource_file', - 'local_file', - 'remote_url', - 'tool_file', -]) +export const zAppLegacyDatasetToolResponse = z.object({ + dataset: zAppDatasetReferenceResponse, +}) + +/** + * AppLegacySensitiveWordToolResponse + */ +export const zAppLegacySensitiveWordToolResponse = z.object({ + canned_response: z.string(), + enabled: z.boolean(), + words: z.array(z.string()), +}) + +/** + * AppLegacySensitiveWordToolResponseItem + */ +export const zAppLegacySensitiveWordToolResponseItem = z.object({ + 'sensitive-word-avoidance': zAppLegacySensitiveWordToolResponse, +}) + +/** + * AppAgentModeResponse + */ +export const zAppAgentModeResponse = z.object({ + enabled: z.boolean(), + max_iteration: z.int().optional(), + prompt: z.union([zAppAgentPromptResponse, z.string()]).nullish(), + strategy: z.union([zPlanningStrategy, z.enum(['cot', 'function-calling'])]).nullish(), + tools: z + .array( + z.union([ + zAppProviderAgentToolResponse, + zAppLegacyDatasetToolResponse, + zAppLegacyGoogleSearchToolResponse, + zAppLegacyWebReaderToolResponse, + zAppLegacyWikipediaToolResponse, + zAppLegacyCurrentDatetimeToolResponse, + zAppLegacySensitiveWordToolResponseItem, + ]), + ) + .optional(), +}) + +/** + * AppDatasetItemResponse + */ +export const zAppDatasetItemResponse = z.object({ + dataset: zAppDatasetReferenceResponse, +}) + +/** + * AppDatasetListResponse + */ +export const zAppDatasetListResponse = z.object({ + datasets: z.array(zAppDatasetItemResponse), + strategy: z.string().optional(), +}) + +/** + * AppMetadataConditionResponse + */ +export const zAppMetadataConditionResponse = z.object({ + comparison_operator: z.enum([ + '<', + '=', + '>', + 'after', + 'before', + 'contains', + 'empty', + 'end with', + 'in', + 'is', + 'is not', + 'not contains', + 'not empty', + 'not in', + 'start with', + '≠', + '≤', + '≥', + ]), + id: z.string().optional(), + metadata_id: z.string().optional(), + name: z.string(), + value: z.union([z.string(), z.array(z.string()), z.int(), z.number()]).nullish(), +}) + +/** + * AppMetadataFilteringConditionsResponse + */ +export const zAppMetadataFilteringConditionsResponse = z.object({ + conditions: z.array(zAppMetadataConditionResponse).nullish(), + logical_operator: z.enum(['and', 'or']).nullish(), +}) + +/** + * AppKeywordSettingResponse + */ +export const zAppKeywordSettingResponse = z.object({ + keyword_weight: z.number(), +}) + +/** + * AppVectorSettingResponse + */ +export const zAppVectorSettingResponse = z.object({ + embedding_model_name: z.string(), + embedding_provider_name: z.string(), + vector_weight: z.number(), +}) + +/** + * AppWeightsResponse + */ +export const zAppWeightsResponse = z.object({ + keyword_setting: zAppKeywordSettingResponse, + vector_setting: zAppVectorSettingResponse, + weight_type: z.enum(['customized', 'keyword_first', 'semantic_first']).optional(), +}) + +/** + * AppDatasetConfigsResponse + */ +export const zAppDatasetConfigsResponse = z.object({ + datasets: zAppDatasetListResponse.optional(), + metadata_filtering_conditions: zAppMetadataFilteringConditionsResponse.nullish(), + metadata_filtering_mode: z.enum(['automatic', 'disabled', 'manual']).optional(), + metadata_model_config: zAppModelSelectionResponse.nullish(), + reranking_enable: z.boolean().optional(), + reranking_enabled: z.boolean().optional(), + reranking_mode: zRerankMode.optional(), + reranking_model: zAppRerankingModelResponse.nullish(), + retrieval_model: z.enum(['multiple', 'single']), + score_threshold: z.number().nullish(), + score_threshold_enabled: z.boolean().optional(), + top_k: z.int().optional(), + weights: zAppWeightsResponse.nullish(), +}) + +/** + * AppModelConfigResponse + */ +export const zAppModelConfigResponse = z.object({ + agent_mode: zAppAgentModeResponse, + annotation_reply: z.union([ + zAppAnnotationReplyEnabledResponse, + zAppAnnotationReplyDisabledResponse, + ]), + chat_prompt_config: zAppChatPromptConfigResponse, + completion_prompt_config: zAppCompletionPromptConfigResponse, + created_at: z.int(), + created_by: z.string().nullable(), + dataset_configs: zAppDatasetConfigsResponse, + dataset_query_variable: z.string().nullable(), + external_data_tools: z.array( + z.union([zAppEnabledExternalDataToolResponse, zAppDisabledExternalDataToolResponse]), + ), + file_upload: zAppFileUploadResponse, + model: zAppModelSelectionResponse, + more_like_this: zAppEnabledConfigResponse, + opening_statement: z.string().nullable(), + pre_prompt: z.string().nullable(), + prompt_type: zPromptType, + retriever_resource: zAppEnabledConfigResponse, + sensitive_word_avoidance: zAppSensitiveWordAvoidanceResponse, + speech_to_text: zAppEnabledConfigResponse, + suggested_questions: z.array(z.string()), + suggested_questions_after_answer: zAppSuggestedQuestionsAfterAnswerResponse, + text_to_speech: zAppTextToSpeechResponse, + updated_at: z.int(), + updated_by: z.string().nullable(), + user_input_form: z.array( + z.union([ + zAppTextInputFormResponse, + zAppSelectFormResponse, + zAppParagraphFormResponse, + zAppNumberFormResponse, + zAppCheckboxFormResponse, + zAppFileFormResponse, + zAppFileListFormResponse, + zAppExternalDataToolFormResponse, + zAppJsonObjectFormResponse, + ]), + ), +}) + +/** + * AgentAppDetailWithSite + */ +export const zAgentAppDetailWithSite = z.object({ + access_mode: zWebAppAccessMode.nullable(), + access_ready: z.boolean().optional().default(false), + api_base_url: z.string(), + app_id: z.string().nullish(), + backing_app_id: z.string().nullish(), + bound_agent_id: z.string().nullish(), + created_at: z.int(), + created_by: z.string().nullable(), + debug_conversation_has_messages: z.boolean().optional().default(false), + debug_conversation_id: z.string().nullish(), + debug_conversation_message_count: z.int().optional().default(0), + deleted_tools: z.array(zDeletedTool), + description: z.string(), + enable_api: z.boolean(), + enable_site: z.boolean(), + hidden_app_backed: z.boolean().optional().default(false), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), + icon_url: z.string().nullable(), + id: z.string(), + maintainer: z.string().nullable(), + max_active_requests: z.int().nullable(), + mode: zAppMode, + model_config: zAppModelConfigResponse.nullable(), + name: z.string(), + permission_keys: z.array(z.string()), + role: z.string().nullish(), + site: zAppDetailSiteResponse.nullable(), + tags: z.array(zTag), + tracing: z.string().nullable(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), + workflow: zWorkflowPartial.nullable(), +}) /** * AgentFileUploadImageFeatureConfig @@ -1930,21 +2467,6 @@ export const zAgentSoulDifyToolCredentialRef = z.object({ type: z.enum(['provider', 'tool']).optional().default('tool'), }) -/** - * ToolProviderType - * - * Enum class for tool provider - */ -export const zToolProviderType = z.enum([ - 'api', - 'app', - 'builtin', - 'dataset-retrieval', - 'mcp', - 'plugin', - 'workflow', -]) - /** * AgentSoulDifyToolConfig * @@ -2508,7 +3030,7 @@ export const zMessageInfiniteScrollPaginationResponse = z.object({ * AgentAppPartial */ export const zAgentAppPartialWritable = z.object({ - access_mode: z.string().nullish(), + access_mode: zWebAppAccessMode.nullish(), active_config_is_published: z.boolean().optional().default(false), app_id: z.string().nullish(), author_name: z.string().nullish(), @@ -2523,12 +3045,12 @@ export const zAgentAppPartialWritable = z.object({ hidden_app_backed: z.boolean().optional().default(false), icon: z.string().nullish(), icon_background: z.string().nullish(), - icon_type: z.string().nullish(), + icon_type: zIconType.nullish(), id: z.string(), is_starred: z.boolean().optional().default(false), maintainer: z.string().nullish(), max_active_requests: z.int().nullish(), - mode: z.string(), + mode: zAppMode, model_config: zModelConfigPartial.nullish(), name: z.string(), permission_keys: z.array(z.string()), @@ -2559,70 +3081,70 @@ export const zAgentAppPaginationWritable = z.object({ * AppDetailSiteResponse */ export const zAppDetailSiteResponseWritable = z.object({ - access_token: z.string().nullish(), - app_base_url: z.string().nullish(), - chat_color_theme: z.string().nullish(), - chat_color_theme_inverted: z.boolean().nullish(), - code: z.string().nullish(), - copyright: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - custom_disclaimer: z.string().nullish(), - customize_domain: z.string().nullish(), - customize_token_strategy: z.string().nullish(), - default_language: z.string().nullish(), - description: z.string().nullish(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.union([z.string(), zIconType]).nullish(), - input_placeholder: z.string().nullish(), - privacy_policy: z.string().nullish(), - prompt_public: z.boolean().nullish(), - show_workflow_steps: z.boolean().nullish(), - title: z.string().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), + access_token: z.string().nullable(), + app_base_url: z.string(), + chat_color_theme: z.string().nullable(), + chat_color_theme_inverted: z.boolean(), + code: z.string().nullable(), + copyright: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + custom_disclaimer: z.string(), + customize_domain: z.string().nullable(), + customize_token_strategy: zCustomizeTokenStrategy, + default_language: z.string(), + description: z.string().nullable(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), + input_placeholder: z.string().nullable(), + privacy_policy: z.string().nullable(), + prompt_public: z.boolean(), + show_workflow_steps: z.boolean(), + title: z.string(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), }) /** * AgentAppDetailWithSite */ export const zAgentAppDetailWithSiteWritable = z.object({ - access_mode: z.string().nullish(), + access_mode: zWebAppAccessMode.nullable(), access_ready: z.boolean().optional().default(false), - api_base_url: z.string().nullish(), + api_base_url: z.string(), app_id: z.string().nullish(), backing_app_id: z.string().nullish(), bound_agent_id: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), + created_at: z.int(), + created_by: z.string().nullable(), debug_conversation_has_messages: z.boolean().optional().default(false), debug_conversation_id: z.string().nullish(), debug_conversation_message_count: z.int().optional().default(0), - deleted_tools: z.array(zDeletedTool).optional(), - description: z.string().nullish(), + deleted_tools: z.array(zDeletedTool), + description: z.string(), enable_api: z.boolean(), enable_site: z.boolean(), hidden_app_backed: z.boolean().optional().default(false), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), id: z.string(), - maintainer: z.string().nullish(), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zAppModelConfigResponse.nullish(), + maintainer: z.string().nullable(), + max_active_requests: z.int().nullable(), + mode: zAppMode, + model_config: zAppModelConfigResponse.nullable(), name: z.string(), permission_keys: z.array(z.string()), role: z.string().nullish(), - site: zAppDetailSiteResponseWritable.nullish(), - tags: z.array(zTag).optional(), - tracing: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), + site: zAppDetailSiteResponseWritable.nullable(), + tags: z.array(zTag), + tracing: z.string().nullable(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), + workflow: zWorkflowPartial.nullable(), }) export const zGetAgentQuery = z.object({ diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index 76366c70c03..c2eac7080ea 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -22,34 +22,34 @@ export type CreateAppPayload = { } export type AppDetailWithSite = { - access_mode?: string | null - api_base_url?: string | null - app_id?: string | null - bound_agent_id?: string | null - created_at?: number | null - created_by?: string | null - deleted_tools?: Array - description?: string | null + access_mode: WebAppAccessMode | null + api_base_url: string + app_id: string | null + bound_agent_id: string | null + created_at: number + created_by: string | null + deleted_tools: Array + description: string enable_api: boolean enable_site: boolean - icon?: string | null - icon_background?: string | null - icon_type?: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null readonly icon_url: string | null id: string - maintainer?: string | null - max_active_requests?: number | null - mode: string - model_config?: AppModelConfigResponse | null + maintainer: string | null + max_active_requests: number | null + mode: AppMode + model_config: AppModelConfigResponse | null name: string - permission_keys?: Array - site?: AppDetailSiteResponse | null - tags?: Array - tracing?: unknown | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null - workflow?: WorkflowPartial | null + permission_keys: Array + site: AppDetailSiteResponse | null + tags: Array + tracing: string | null + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean + workflow: WorkflowPartial | null } export type AppImportPayload = { @@ -66,7 +66,7 @@ export type AppImportPayload = { export type Import = { app_id?: string | null - app_mode?: string | null + app_mode?: AppMode | null current_dsl_version?: string error?: string id: string @@ -362,26 +362,26 @@ export type AppApiStatusPayload = { } export type AppDetail = { - access_mode?: string | null - created_at?: number | null - created_by?: string | null - description?: string | null + access_mode: WebAppAccessMode | null + created_at: number + created_by: string | null + description: string enable_api: boolean enable_site: boolean - icon?: string | null - icon_background?: string | null + icon: string | null + icon_background: string | null id: string - maintainer?: string | null - mode: string - model_config?: AppModelConfigResponse | null + maintainer: string | null + mode: AppMode + model_config: AppModelConfigResponse | null name: string - permission_keys?: Array - tags?: Array - tracing?: unknown | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null - workflow?: WorkflowPartial | null + permission_keys: Array + tags: Array + tracing: string | null + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean + workflow: WorkflowPartial | null } export type AudioTranscriptResponse = { @@ -488,7 +488,7 @@ export type CopyAppPayload = { export type AppImportResponse = { app_id?: string | null - app_mode?: string | null + app_mode?: AppMode | null current_dsl_version: string error?: string id: string @@ -1199,7 +1199,7 @@ export type ApiKeyItem = { } export type AppPartial = { - access_mode?: string | null + access_mode?: WebAppAccessMode | null app_id?: string | null author_name?: string | null bound_agent_id?: string | null @@ -1210,13 +1210,13 @@ export type AppPartial = { has_draft_trigger?: boolean | null icon?: string | null icon_background?: string | null - icon_type?: string | null + icon_type?: IconType | null readonly icon_url: string | null id: string is_starred?: boolean maintainer?: string | null max_active_requests?: number | null - mode: string + mode: AppMode model_config?: ModelConfigPartial | null name: string permission_keys?: Array @@ -1229,65 +1229,89 @@ export type AppPartial = { export type IconType = 'emoji' | 'image' | 'link' +export type WebAppAccessMode = 'private' | 'private_all' | 'public' | 'sso_verified' + export type DeletedTool = { provider_id: string tool_name: string type: string } +export type AppMode = + | 'advanced-chat' + | 'agent' + | 'agent-chat' + | 'channel' + | 'chat' + | 'completion' + | 'rag-pipeline' + | 'workflow' + export type AppModelConfigResponse = { - agent_mode?: unknown | null - annotation_reply?: unknown | null - chat_prompt_config?: unknown | null - completion_prompt_config?: unknown | null - created_at?: number | null - created_by?: string | null - dataset_configs?: unknown | null - dataset_query_variable?: string | null - external_data_tools?: unknown | null - file_upload?: unknown | null - model?: unknown | null - more_like_this?: unknown | null - opening_statement?: string | null - pre_prompt?: string | null - prompt_type?: string | null - retriever_resource?: unknown | null - sensitive_word_avoidance?: unknown | null - speech_to_text?: unknown | null - suggested_questions?: unknown | null - suggested_questions_after_answer?: unknown | null - text_to_speech?: unknown | null - updated_at?: number | null - updated_by?: string | null - user_input_form?: unknown | null + agent_mode: AppAgentModeResponse + annotation_reply: AppAnnotationReplyEnabledResponse | AppAnnotationReplyDisabledResponse + chat_prompt_config: AppChatPromptConfigResponse + completion_prompt_config: AppCompletionPromptConfigResponse + created_at: number + created_by: string | null + dataset_configs: AppDatasetConfigsResponse + dataset_query_variable: string | null + external_data_tools: Array< + AppEnabledExternalDataToolResponse | AppDisabledExternalDataToolResponse + > + file_upload: AppFileUploadResponse + model: AppModelSelectionResponse + more_like_this: AppEnabledConfigResponse + opening_statement: string | null + pre_prompt: string | null + prompt_type: PromptType + retriever_resource: AppEnabledConfigResponse + sensitive_word_avoidance: AppSensitiveWordAvoidanceResponse + speech_to_text: AppEnabledConfigResponse + suggested_questions: Array + suggested_questions_after_answer: AppSuggestedQuestionsAfterAnswerResponse + text_to_speech: AppTextToSpeechResponse + updated_at: number + updated_by: string | null + user_input_form: Array< + | AppTextInputFormResponse + | AppSelectFormResponse + | AppParagraphFormResponse + | AppNumberFormResponse + | AppCheckboxFormResponse + | AppFileFormResponse + | AppFileListFormResponse + | AppExternalDataToolFormResponse + | AppJsonObjectFormResponse + > } export type AppDetailSiteResponse = { - access_token?: string | null - app_base_url?: string | null - chat_color_theme?: string | null - chat_color_theme_inverted?: boolean | null - code?: string | null - copyright?: string | null - created_at?: number | null - created_by?: string | null - custom_disclaimer?: string | null - customize_domain?: string | null - customize_token_strategy?: string | null - default_language?: string | null - description?: string | null - icon?: string | null - icon_background?: string | null - icon_type?: string | IconType | null + access_token: string | null + app_base_url: string + chat_color_theme: string | null + chat_color_theme_inverted: boolean + code: string | null + copyright: string | null + created_at: number + created_by: string | null + custom_disclaimer: string + customize_domain: string | null + customize_token_strategy: CustomizeTokenStrategy + default_language: string + description: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null readonly icon_url: string | null - input_placeholder?: string | null - privacy_policy?: string | null - prompt_public?: boolean | null - show_workflow_steps?: boolean | null - title?: string | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null + input_placeholder: string | null + privacy_policy: string | null + prompt_public: boolean + show_workflow_steps: boolean + title: string + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean } export type Tag = { @@ -2088,12 +2112,178 @@ export type WorkflowDraftVariableFullContentResponse = { export type ModelConfigPartial = { created_at?: number | null created_by?: string | null - model?: unknown | null + model?: AppModelSelectionResponse | null pre_prompt?: string | null updated_at?: number | null updated_by?: string | null } +export type AppAgentModeResponse = { + enabled: boolean + max_iteration?: number + prompt?: AppAgentPromptResponse | string | null + strategy?: PlanningStrategy | 'cot' | 'function-calling' | null + tools?: Array< + | AppProviderAgentToolResponse + | AppLegacyDatasetToolResponse + | AppLegacyGoogleSearchToolResponse + | AppLegacyWebReaderToolResponse + | AppLegacyWikipediaToolResponse + | AppLegacyCurrentDatetimeToolResponse + | AppLegacySensitiveWordToolResponseItem + > + [key: string]: unknown +} + +export type AppAnnotationReplyEnabledResponse = { + embedding_model: AppEmbeddingModelResponse + enabled: true + id: string + score_threshold: number +} + +export type AppAnnotationReplyDisabledResponse = { + enabled: false +} + +export type AppChatPromptConfigResponse = { + prompt?: Array +} + +export type AppCompletionPromptConfigResponse = { + conversation_histories_role?: AppConversationHistoriesRoleResponse + prompt?: AppCompletionPromptTextResponse +} + +export type AppDatasetConfigsResponse = { + datasets?: AppDatasetListResponse + metadata_filtering_conditions?: AppMetadataFilteringConditionsResponse | null + metadata_filtering_mode?: 'automatic' | 'disabled' | 'manual' + metadata_model_config?: AppModelSelectionResponse | null + reranking_enable?: boolean + reranking_enabled?: boolean + reranking_mode?: RerankMode + reranking_model?: AppRerankingModelResponse | null + retrieval_model: 'multiple' | 'single' + score_threshold?: number | null + score_threshold_enabled?: boolean + top_k?: number + weights?: AppWeightsResponse | null + [key: string]: unknown +} + +export type AppEnabledExternalDataToolResponse = { + config: { + [key: string]: JsonValue2 + } + enabled: true + icon?: string + icon_background?: string + label?: string + type: string + variable: string + [key: string]: unknown +} + +export type AppDisabledExternalDataToolResponse = { + config?: { + [key: string]: JsonValue2 + } + enabled: false + icon?: string + icon_background?: string + label?: string + type?: string + variable?: string + [key: string]: unknown +} + +export type AppFileUploadResponse = { + allowed_file_extensions?: Array + allowed_file_types?: Array + allowed_file_upload_methods?: Array + enabled?: boolean + image?: AppImageUploadResponse + number_limits?: number +} + +export type AppModelSelectionResponse = { + completion_params?: { + [key: string]: JsonValue2 + } + mode?: LlmMode | '' + name?: string + provider?: string + [key: string]: unknown +} + +export type AppEnabledConfigResponse = { + enabled: boolean +} + +export type PromptType = 'advanced' | 'simple' + +export type AppSensitiveWordAvoidanceResponse = { + config?: { + [key: string]: JsonValue2 + } + configs?: Array + enabled: boolean + type?: string + [key: string]: unknown +} + +export type AppSuggestedQuestionsAfterAnswerResponse = { + enabled: boolean + model?: AppModelSelectionResponse + prompt?: string +} + +export type AppTextToSpeechResponse = { + autoPlay?: 'disabled' | 'enabled' + enabled: boolean + language?: string + voice?: string +} + +export type AppTextInputFormResponse = { + 'text-input': AppUserInputFormConfigResponse +} + +export type AppSelectFormResponse = { + select: AppUserInputFormConfigResponse +} + +export type AppParagraphFormResponse = { + paragraph: AppUserInputFormConfigResponse +} + +export type AppNumberFormResponse = { + number: AppUserInputFormConfigResponse +} + +export type AppCheckboxFormResponse = { + checkbox: AppUserInputFormConfigResponse +} + +export type AppFileFormResponse = { + file: AppUserInputFormConfigResponse +} + +export type AppFileListFormResponse = { + 'file-list': AppUserInputFormConfigResponse +} + +export type AppExternalDataToolFormResponse = { + external_data_tool: AppUserInputFormConfigResponse +} + +export type AppJsonObjectFormResponse = { + json_object: AppUserInputFormConfigResponse +} + +export type CustomizeTokenStrategy = 'allow' | 'must' | 'not_allow' | 'uuid' + export type PluginDependencyType = 'github' | 'marketplace' | 'package' export type Github = { @@ -2250,7 +2440,7 @@ export type WorkflowSuggestedQuestionsAfterAnswerPayload = { } export type WorkflowTextToSpeechPayload = { - autoPlay?: string | null + autoPlay?: 'disabled' | 'enabled' | null enabled?: boolean | null language?: string | null voice?: string | null @@ -2484,6 +2674,156 @@ export type CheckResultView = { reason?: string | null } +export type AppAgentPromptResponse = { + first_prompt?: string + next_iteration?: string + [key: string]: unknown +} + +export type PlanningStrategy = 'function_call' | 'react' | 'react_router' | 'router' + +export type AppProviderAgentToolResponse = { + credential_id?: string | null + enabled?: boolean + isDeleted?: boolean + notAuthor?: boolean + plugin_unique_identifier?: string | null + provider_id: string + provider_name?: string + provider_type: ToolProviderType + tool_label?: string + tool_name: string + tool_parameters: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyDatasetToolResponse = { + dataset: AppDatasetReferenceResponse + [key: string]: unknown +} + +export type AppLegacyGoogleSearchToolResponse = { + google_search: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyWebReaderToolResponse = { + web_reader: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyWikipediaToolResponse = { + wikipedia: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacyCurrentDatetimeToolResponse = { + current_datetime: { + [key: string]: JsonValue2 + } + [key: string]: unknown +} + +export type AppLegacySensitiveWordToolResponseItem = { + 'sensitive-word-avoidance': AppLegacySensitiveWordToolResponse + [key: string]: unknown +} + +export type AppEmbeddingModelResponse = { + embedding_model_name: string + embedding_provider_name: string +} + +export type AppChatPromptMessageResponse = { + role: string + text: string +} + +export type AppConversationHistoriesRoleResponse = { + assistant_prefix: string + user_prefix: string +} + +export type AppCompletionPromptTextResponse = { + text: string +} + +export type AppDatasetListResponse = { + datasets: Array + strategy?: string + [key: string]: unknown +} + +export type AppMetadataFilteringConditionsResponse = { + conditions?: Array | null + logical_operator?: 'and' | 'or' | null + [key: string]: unknown +} + +export type RerankMode = 'reranking_model' | 'weighted_score' + +export type AppRerankingModelResponse = { + reranking_model_name?: string + reranking_provider_name?: string + [key: string]: unknown +} + +export type AppWeightsResponse = { + keyword_setting: AppKeywordSettingResponse + vector_setting: AppVectorSettingResponse + weight_type?: 'customized' | 'keyword_first' | 'semantic_first' + [key: string]: unknown +} + +export type JsonValue2 = unknown + +export type FileType = 'audio' | 'custom' | 'document' | 'image' | 'video' + +export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url' | 'tool_file' + +export type AppImageUploadResponse = { + detail?: 'high' | 'low' | null + enabled?: boolean + number_limits?: number + transfer_methods?: Array +} + +export type AppUserInputFormConfigResponse = { + allowed_file_extensions?: Array + allowed_file_types?: Array + allowed_file_upload_methods?: Array + config?: { + [key: string]: JsonValue2 + } + default?: JsonValue2 + description?: string + enabled?: boolean + hide?: boolean + icon?: string | null + icon_background?: string | null + json_schema?: + | string + | { + [key: string]: JsonValue2 + } + | null + label: string + max_length?: number | null + options?: Array + required?: boolean + type?: string + variable: string + [key: string]: unknown +} + export type UserActionConfig = { button_style?: ButtonStyle id: string @@ -2504,8 +2844,6 @@ export type FormInputConfig = type: 'file-list' } & FileListInputConfig) -export type JsonValue2 = unknown - export type WorkflowFileUploadTransferPayload = { enabled?: boolean | null number_limits?: number | null @@ -2556,7 +2894,7 @@ export type AgentSuggestedQuestionsAfterAnswerFeatureConfig = { } export type AgentTextToSpeechFeatureConfig = { - autoPlay?: string | null + autoPlay?: 'disabled' | 'enabled' | null enabled?: boolean language?: string | null voice?: string | null @@ -2743,6 +3081,72 @@ export type AgentComposerKnowledgeDatasetCandidateResponse = { name?: string | null } +export type ToolProviderType = + | 'api' + | 'app' + | 'builtin' + | 'dataset-retrieval' + | 'mcp' + | 'plugin' + | 'workflow' + +export type AppDatasetReferenceResponse = { + enabled?: boolean + id?: string + [key: string]: unknown +} + +export type AppLegacySensitiveWordToolResponse = { + canned_response: string + enabled: boolean + words: Array + [key: string]: unknown +} + +export type AppDatasetItemResponse = { + dataset: AppDatasetReferenceResponse + [key: string]: unknown +} + +export type AppMetadataConditionResponse = { + comparison_operator: + | '<' + | '=' + | '>' + | 'after' + | 'before' + | 'contains' + | 'empty' + | 'end with' + | 'in' + | 'is' + | 'is not' + | 'not contains' + | 'not empty' + | 'not in' + | 'start with' + | '≠' + | '≤' + | '≥' + id?: string + metadata_id?: string + name: string + value?: string | Array | number | number | null + [key: string]: unknown +} + +export type AppKeywordSettingResponse = { + keyword_weight: number + [key: string]: unknown +} + +export type AppVectorSettingResponse = { + embedding_model_name: string + embedding_provider_name: string + vector_weight: number + [key: string]: unknown +} + export type ButtonStyle = 'accent' | 'default' | 'ghost' | 'primary' export type ParagraphInputConfig = { @@ -2774,10 +3178,6 @@ export type FileListInputConfig = { type?: 'file-list' } -export type FileType = 'audio' | 'custom' | 'document' | 'image' | 'video' - -export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url' | 'tool_file' - export type AgentFileUploadImageFeatureConfig = { enabled?: boolean [key: string]: unknown @@ -2840,15 +3240,6 @@ export type AgentSoulDifyToolCredentialRef = { type?: 'provider' | 'tool' } -export type ToolProviderType = - | 'api' - | 'app' - | 'builtin' - | 'dataset-retrieval' - | 'mcp' - | 'plugin' - | 'workflow' - export type StringSource = { selector?: Array type: ValueSourceType @@ -2936,33 +3327,33 @@ export type AppPaginationWritable = { } export type AppDetailWithSiteWritable = { - access_mode?: string | null - api_base_url?: string | null - app_id?: string | null - bound_agent_id?: string | null - created_at?: number | null - created_by?: string | null - deleted_tools?: Array - description?: string | null + access_mode: WebAppAccessMode | null + api_base_url: string + app_id: string | null + bound_agent_id: string | null + created_at: number + created_by: string | null + deleted_tools: Array + description: string enable_api: boolean enable_site: boolean - icon?: string | null - icon_background?: string | null - icon_type?: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null id: string - maintainer?: string | null - max_active_requests?: number | null - mode: string - model_config?: AppModelConfigResponse | null + maintainer: string | null + max_active_requests: number | null + mode: AppMode + model_config: AppModelConfigResponse | null name: string - permission_keys?: Array - site?: AppDetailSiteResponseWritable | null - tags?: Array - tracing?: unknown | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null - workflow?: WorkflowPartial | null + permission_keys: Array + site: AppDetailSiteResponseWritable | null + tags: Array + tracing: string | null + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean + workflow: WorkflowPartial | null } export type RecentAppListResponseWritable = { @@ -2997,7 +3388,7 @@ export type WorkflowCommentDetailWritable = { } export type AppPartialWritable = { - access_mode?: string | null + access_mode?: WebAppAccessMode | null app_id?: string | null author_name?: string | null bound_agent_id?: string | null @@ -3008,12 +3399,12 @@ export type AppPartialWritable = { has_draft_trigger?: boolean | null icon?: string | null icon_background?: string | null - icon_type?: string | null + icon_type?: IconType | null id: string is_starred?: boolean maintainer?: string | null max_active_requests?: number | null - mode: string + mode: AppMode model_config?: ModelConfigPartial | null name: string permission_keys?: Array @@ -3025,30 +3416,30 @@ export type AppPartialWritable = { } export type AppDetailSiteResponseWritable = { - access_token?: string | null - app_base_url?: string | null - chat_color_theme?: string | null - chat_color_theme_inverted?: boolean | null - code?: string | null - copyright?: string | null - created_at?: number | null - created_by?: string | null - custom_disclaimer?: string | null - customize_domain?: string | null - customize_token_strategy?: string | null - default_language?: string | null - description?: string | null - icon?: string | null - icon_background?: string | null - icon_type?: string | IconType | null - input_placeholder?: string | null - privacy_policy?: string | null - prompt_public?: boolean | null - show_workflow_steps?: boolean | null - title?: string | null - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null + access_token: string | null + app_base_url: string + chat_color_theme: string | null + chat_color_theme_inverted: boolean + code: string | null + copyright: string | null + created_at: number + created_by: string | null + custom_disclaimer: string + customize_domain: string | null + customize_token_strategy: CustomizeTokenStrategy + default_language: string + description: string | null + icon: string | null + icon_background: string | null + icon_type: IconType | null + input_placeholder: string | null + privacy_policy: string | null + prompt_public: boolean + show_workflow_steps: boolean + title: string + updated_at: number + updated_by: string | null + use_icon_as_answer_icon: boolean } export type RecentAppResponseWritable = { diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index 8572e7e4244..50ab21e35f8 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -781,6 +781,11 @@ export const zAppIconPayload = z.object({ icon_type: zIconType.nullish(), }) +/** + * WebAppAccessMode + */ +export const zWebAppAccessMode = z.enum(['private', 'private_all', 'public', 'sso_verified']) + /** * DeletedTool */ @@ -791,65 +796,18 @@ export const zDeletedTool = z.object({ }) /** - * AppModelConfigResponse + * AppMode */ -export const zAppModelConfigResponse = z.object({ - agent_mode: z.unknown().nullish(), - annotation_reply: z.unknown().nullish(), - chat_prompt_config: z.unknown().nullish(), - completion_prompt_config: z.unknown().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - dataset_configs: z.unknown().nullish(), - dataset_query_variable: z.string().nullish(), - external_data_tools: z.unknown().nullish(), - file_upload: z.unknown().nullish(), - model: z.unknown().nullish(), - more_like_this: z.unknown().nullish(), - opening_statement: z.string().nullish(), - pre_prompt: z.string().nullish(), - prompt_type: z.string().nullish(), - retriever_resource: z.unknown().nullish(), - sensitive_word_avoidance: z.unknown().nullish(), - speech_to_text: z.unknown().nullish(), - suggested_questions: z.unknown().nullish(), - suggested_questions_after_answer: z.unknown().nullish(), - text_to_speech: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - user_input_form: z.unknown().nullish(), -}) - -/** - * AppDetailSiteResponse - */ -export const zAppDetailSiteResponse = z.object({ - access_token: z.string().nullish(), - app_base_url: z.string().nullish(), - chat_color_theme: z.string().nullish(), - chat_color_theme_inverted: z.boolean().nullish(), - code: z.string().nullish(), - copyright: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - custom_disclaimer: z.string().nullish(), - customize_domain: z.string().nullish(), - customize_token_strategy: z.string().nullish(), - default_language: z.string().nullish(), - description: z.string().nullish(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.union([z.string(), zIconType]).nullish(), - icon_url: z.string().nullable(), - input_placeholder: z.string().nullish(), - privacy_policy: z.string().nullish(), - prompt_public: z.boolean().nullish(), - show_workflow_steps: z.boolean().nullish(), - title: z.string().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), -}) +export const zAppMode = z.enum([ + 'advanced-chat', + 'agent', + 'agent-chat', + 'channel', + 'chat', + 'completion', + 'rag-pipeline', + 'workflow', +]) /** * Tag @@ -871,66 +829,6 @@ export const zWorkflowPartial = z.object({ updated_by: z.string().nullish(), }) -/** - * AppDetailWithSite - */ -export const zAppDetailWithSite = z.object({ - access_mode: z.string().nullish(), - api_base_url: z.string().nullish(), - app_id: z.string().nullish(), - bound_agent_id: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - deleted_tools: z.array(zDeletedTool).optional(), - description: z.string().nullish(), - enable_api: z.boolean(), - enable_site: z.boolean(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), - icon_url: z.string().nullable(), - id: z.string(), - maintainer: z.string().nullish(), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zAppModelConfigResponse.nullish(), - name: z.string(), - permission_keys: z.array(z.string()).optional(), - site: zAppDetailSiteResponse.nullish(), - tags: z.array(zTag).optional(), - tracing: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), -}) - -/** - * AppDetail - */ -export const zAppDetail = z.object({ - access_mode: z.string().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(), - maintainer: z.string().nullish(), - mode: z.string(), - model_config: zAppModelConfigResponse.nullish(), - name: z.string(), - permission_keys: z.array(z.string()).optional(), - tags: z.array(zTag).optional(), - tracing: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), -}) - /** * ImportStatus */ @@ -953,7 +851,7 @@ export const zDslImportWarning = z.object({ */ export const zImport = z.object({ app_id: z.string().nullish(), - app_mode: z.string().nullish(), + app_mode: zAppMode.nullish(), current_dsl_version: z.string().optional().default('0.7.0'), error: z.string().optional().default(''), id: z.string(), @@ -968,7 +866,7 @@ export const zImport = z.object({ */ export const zAppImportResponse = z.object({ app_id: z.string().nullish(), - app_mode: z.string().nullish(), + app_mode: zAppMode.nullish(), current_dsl_version: z.string(), error: z.string().optional().default(''), id: z.string(), @@ -2164,58 +2062,72 @@ export const zWorkflowDraftVariableListResponse = z.object({ }) /** - * ModelConfigPartial + * AppAnnotationReplyDisabledResponse */ -export const zModelConfigPartial = z.object({ - created_at: z.int().nullish(), - created_by: z.string().nullish(), - model: z.unknown().nullish(), - pre_prompt: z.string().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), +export const zAppAnnotationReplyDisabledResponse = z.object({ + enabled: z.literal(false), }) /** - * AppPartial + * AppEnabledConfigResponse */ -export const zAppPartial = z.object({ - access_mode: z.string().nullish(), - app_id: z.string().nullish(), - author_name: z.string().nullish(), - bound_agent_id: z.string().nullish(), - create_user_name: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - description: z.string().nullish(), - has_draft_trigger: z.boolean().nullish(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), +export const zAppEnabledConfigResponse = z.object({ + enabled: z.boolean(), +}) + +/** + * PromptType + * + * Prompt configuration type + */ +export const zPromptType = z.enum(['advanced', 'simple']) + +/** + * AppTextToSpeechResponse + */ +export const zAppTextToSpeechResponse = z.object({ + autoPlay: z.enum(['disabled', 'enabled']).optional(), + enabled: z.boolean(), + language: z.string().optional(), + voice: z.string().optional(), +}) + +/** + * CustomizeTokenStrategy + * + * Site token customization strategy + */ +export const zCustomizeTokenStrategy = z.enum(['allow', 'must', 'not_allow', 'uuid']) + +/** + * AppDetailSiteResponse + */ +export const zAppDetailSiteResponse = z.object({ + access_token: z.string().nullable(), + app_base_url: z.string(), + chat_color_theme: z.string().nullable(), + chat_color_theme_inverted: z.boolean(), + code: z.string().nullable(), + copyright: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + custom_disclaimer: z.string(), + customize_domain: z.string().nullable(), + customize_token_strategy: zCustomizeTokenStrategy, + default_language: z.string(), + description: z.string().nullable(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), icon_url: z.string().nullable(), - id: z.string(), - is_starred: z.boolean().optional().default(false), - maintainer: z.string().nullish(), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zModelConfigPartial.nullish(), - name: z.string(), - permission_keys: z.array(z.string()).optional(), - tags: z.array(zTag).optional(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), -}) - -/** - * AppPagination - */ -export const zAppPagination = z.object({ - data: z.array(zAppPartial), - has_more: z.boolean(), - limit: z.int(), - page: z.int(), - total: z.int(), + input_placeholder: z.string().nullable(), + privacy_policy: z.string().nullable(), + prompt_public: z.boolean(), + show_workflow_steps: z.boolean(), + title: z.string(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), }) /** @@ -2625,7 +2537,7 @@ export const zWorkflowSuggestedQuestionsAfterAnswerPayload = z.object({ * WorkflowTextToSpeechPayload */ export const zWorkflowTextToSpeechPayload = z.object({ - autoPlay: z.string().nullish(), + autoPlay: z.enum(['disabled', 'enabled']).nullish(), enabled: z.boolean().nullish(), language: z.string().nullish(), voice: z.string().nullish(), @@ -2888,8 +2800,200 @@ export const zWorkflowRunSnapshotView = z.object({ workflow_run_status: zWorkflowExecutionStatus, }) +/** + * AppAgentPromptResponse + */ +export const zAppAgentPromptResponse = z.object({ + first_prompt: z.string().optional(), + next_iteration: z.string().optional(), +}) + +/** + * PlanningStrategy + */ +export const zPlanningStrategy = z.enum(['function_call', 'react', 'react_router', 'router']) + +/** + * AppEmbeddingModelResponse + */ +export const zAppEmbeddingModelResponse = z.object({ + embedding_model_name: z.string(), + embedding_provider_name: z.string(), +}) + +/** + * AppAnnotationReplyEnabledResponse + */ +export const zAppAnnotationReplyEnabledResponse = z.object({ + embedding_model: zAppEmbeddingModelResponse, + enabled: z.literal(true), + id: z.string(), + score_threshold: z.number(), +}) + +/** + * AppChatPromptMessageResponse + */ +export const zAppChatPromptMessageResponse = z.object({ + role: z.string(), + text: z.string(), +}) + +/** + * AppChatPromptConfigResponse + */ +export const zAppChatPromptConfigResponse = z.object({ + prompt: z.array(zAppChatPromptMessageResponse).optional(), +}) + +/** + * AppConversationHistoriesRoleResponse + */ +export const zAppConversationHistoriesRoleResponse = z.object({ + assistant_prefix: z.string(), + user_prefix: z.string(), +}) + +/** + * AppCompletionPromptTextResponse + */ +export const zAppCompletionPromptTextResponse = z.object({ + text: z.string(), +}) + +/** + * AppCompletionPromptConfigResponse + */ +export const zAppCompletionPromptConfigResponse = z.object({ + conversation_histories_role: zAppConversationHistoriesRoleResponse.optional(), + prompt: zAppCompletionPromptTextResponse.optional(), +}) + +/** + * RerankMode + */ +export const zRerankMode = z.enum(['reranking_model', 'weighted_score']) + +/** + * AppRerankingModelResponse + */ +export const zAppRerankingModelResponse = z.object({ + reranking_model_name: z.string().optional(), + reranking_provider_name: z.string().optional(), +}) + export const zJsonValue2 = z.unknown() +/** + * AppEnabledExternalDataToolResponse + */ +export const zAppEnabledExternalDataToolResponse = z.object({ + config: z.record(z.string(), zJsonValue2), + enabled: z.literal(true), + icon: z.string().optional(), + icon_background: z.string().optional(), + label: z.string().optional(), + type: z.string(), + variable: z.string(), +}) + +/** + * AppDisabledExternalDataToolResponse + */ +export const zAppDisabledExternalDataToolResponse = z.object({ + config: z.record(z.string(), zJsonValue2).optional(), + enabled: z.literal(false), + icon: z.string().optional(), + icon_background: z.string().optional(), + label: z.string().optional(), + type: z.string().optional(), + variable: z.string().optional(), +}) + +/** + * AppModelSelectionResponse + */ +export const zAppModelSelectionResponse = z.object({ + completion_params: z.record(z.string(), zJsonValue2).optional(), + mode: z.union([zLlmMode, z.literal('')]).optional(), + name: z.string().optional(), + provider: z.string().optional(), +}) + +/** + * ModelConfigPartial + */ +export const zModelConfigPartial = z.object({ + created_at: z.int().nullish(), + created_by: z.string().nullish(), + model: zAppModelSelectionResponse.nullish(), + pre_prompt: z.string().nullish(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), +}) + +/** + * AppPartial + */ +export const zAppPartial = z.object({ + access_mode: zWebAppAccessMode.nullish(), + app_id: z.string().nullish(), + author_name: z.string().nullish(), + bound_agent_id: z.string().nullish(), + create_user_name: z.string().nullish(), + created_at: z.int().nullish(), + created_by: z.string().nullish(), + description: z.string().nullish(), + has_draft_trigger: z.boolean().nullish(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + icon_type: zIconType.nullish(), + icon_url: z.string().nullable(), + id: z.string(), + is_starred: z.boolean().optional().default(false), + maintainer: z.string().nullish(), + max_active_requests: z.int().nullish(), + mode: zAppMode, + model_config: zModelConfigPartial.nullish(), + name: z.string(), + permission_keys: z.array(z.string()).optional(), + tags: z.array(zTag).optional(), + updated_at: z.int().nullish(), + updated_by: z.string().nullish(), + use_icon_as_answer_icon: z.boolean().nullish(), + workflow: zWorkflowPartial.nullish(), +}) + +/** + * AppPagination + */ +export const zAppPagination = z.object({ + data: z.array(zAppPartial), + has_more: z.boolean(), + limit: z.int(), + page: z.int(), + total: z.int(), +}) + +/** + * AppSensitiveWordAvoidanceResponse + */ +export const zAppSensitiveWordAvoidanceResponse = z.object({ + config: z.record(z.string(), zJsonValue2).optional(), + configs: z.array(zJsonValue2).optional(), + enabled: z.boolean(), + type: z.string().optional(), +}) + +/** + * AppSuggestedQuestionsAfterAnswerResponse + */ +export const zAppSuggestedQuestionsAfterAnswerResponse = z.object({ + enabled: z.boolean(), + model: zAppModelSelectionResponse.optional(), + prompt: z.string().optional(), +}) + /** * HumanInputFormSubmissionData */ @@ -2902,6 +3006,157 @@ export const zHumanInputFormSubmissionData = z.object({ submitted_data: z.record(z.string(), zJsonValue2).nullish(), }) +/** + * AppLegacyGoogleSearchToolResponse + */ +export const zAppLegacyGoogleSearchToolResponse = z.object({ + google_search: z.record(z.string(), zJsonValue2), +}) + +/** + * AppLegacyWebReaderToolResponse + */ +export const zAppLegacyWebReaderToolResponse = z.object({ + web_reader: z.record(z.string(), zJsonValue2), +}) + +/** + * AppLegacyWikipediaToolResponse + */ +export const zAppLegacyWikipediaToolResponse = z.object({ + wikipedia: z.record(z.string(), zJsonValue2), +}) + +/** + * AppLegacyCurrentDatetimeToolResponse + */ +export const zAppLegacyCurrentDatetimeToolResponse = z.object({ + current_datetime: z.record(z.string(), zJsonValue2), +}) + +/** + * FileType + */ +export const zFileType = z.enum(['audio', 'custom', 'document', 'image', 'video']) + +/** + * FileTransferMethod + */ +export const zFileTransferMethod = z.enum([ + 'datasource_file', + 'local_file', + 'remote_url', + 'tool_file', +]) + +/** + * AppImageUploadResponse + */ +export const zAppImageUploadResponse = z.object({ + detail: z.enum(['high', 'low']).nullish(), + enabled: z.boolean().optional(), + number_limits: z.int().optional(), + transfer_methods: z.array(zFileTransferMethod).optional(), +}) + +/** + * AppFileUploadResponse + */ +export const zAppFileUploadResponse = z.object({ + allowed_file_extensions: z.array(z.string()).optional(), + allowed_file_types: z.array(zFileType).optional(), + allowed_file_upload_methods: z.array(zFileTransferMethod).optional(), + enabled: z.boolean().optional(), + image: zAppImageUploadResponse.optional(), + number_limits: z.int().optional(), +}) + +/** + * AppUserInputFormConfigResponse + */ +export const zAppUserInputFormConfigResponse = z.object({ + allowed_file_extensions: z.array(z.string()).optional(), + allowed_file_types: z.array(zFileType).optional(), + allowed_file_upload_methods: z.array(zFileTransferMethod).optional(), + config: z.record(z.string(), zJsonValue2).optional(), + default: zJsonValue2.optional(), + description: z.string().optional(), + enabled: z.boolean().optional(), + hide: z.boolean().optional(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + json_schema: z.union([z.string(), z.record(z.string(), zJsonValue2)]).nullish(), + label: z.string(), + max_length: z.int().nullish(), + options: z.array(z.string()).optional(), + required: z.boolean().optional(), + type: z.string().optional(), + variable: z.string(), +}) + +/** + * AppTextInputFormResponse + */ +export const zAppTextInputFormResponse = z.object({ + 'text-input': zAppUserInputFormConfigResponse, +}) + +/** + * AppSelectFormResponse + */ +export const zAppSelectFormResponse = z.object({ + select: zAppUserInputFormConfigResponse, +}) + +/** + * AppParagraphFormResponse + */ +export const zAppParagraphFormResponse = z.object({ + paragraph: zAppUserInputFormConfigResponse, +}) + +/** + * AppNumberFormResponse + */ +export const zAppNumberFormResponse = z.object({ + number: zAppUserInputFormConfigResponse, +}) + +/** + * AppCheckboxFormResponse + */ +export const zAppCheckboxFormResponse = z.object({ + checkbox: zAppUserInputFormConfigResponse, +}) + +/** + * AppFileFormResponse + */ +export const zAppFileFormResponse = z.object({ + file: zAppUserInputFormConfigResponse, +}) + +/** + * AppFileListFormResponse + */ +export const zAppFileListFormResponse = z.object({ + 'file-list': zAppUserInputFormConfigResponse, +}) + +/** + * AppExternalDataToolFormResponse + */ +export const zAppExternalDataToolFormResponse = z.object({ + external_data_tool: zAppUserInputFormConfigResponse, +}) + +/** + * AppJsonObjectFormResponse + */ +export const zAppJsonObjectFormResponse = z.object({ + json_object: zAppUserInputFormConfigResponse, +}) + /** * WorkflowFileUploadTransferPayload */ @@ -2979,7 +3234,7 @@ export const zAgentFeatureToggleConfig = z.object({ * AgentTextToSpeechFeatureConfig */ export const zAgentTextToSpeechFeatureConfig = z.object({ - autoPlay: z.string().nullish(), + autoPlay: z.enum(['disabled', 'enabled']).nullish(), enabled: z.boolean().optional().default(false), language: z.string().nullish(), voice: z.string().nullish(), @@ -3311,6 +3566,296 @@ export const zAgentComposerCandidatesResponse = z.object({ variant: zComposerVariant, }) +/** + * ToolProviderType + * + * Enum class for tool provider + */ +export const zToolProviderType = z.enum([ + 'api', + 'app', + 'builtin', + 'dataset-retrieval', + 'mcp', + 'plugin', + 'workflow', +]) + +/** + * AppProviderAgentToolResponse + */ +export const zAppProviderAgentToolResponse = z.object({ + credential_id: z.string().nullish(), + enabled: z.boolean().optional(), + isDeleted: z.boolean().optional(), + notAuthor: z.boolean().optional(), + plugin_unique_identifier: z.string().nullish(), + provider_id: z.string(), + provider_name: z.string().optional(), + provider_type: zToolProviderType, + tool_label: z.string().optional(), + tool_name: z.string(), + tool_parameters: z.record(z.string(), zJsonValue2), +}) + +/** + * AppDatasetReferenceResponse + */ +export const zAppDatasetReferenceResponse = z.object({ + enabled: z.boolean().optional(), + id: z.string().optional(), +}) + +/** + * AppLegacyDatasetToolResponse + */ +export const zAppLegacyDatasetToolResponse = z.object({ + dataset: zAppDatasetReferenceResponse, +}) + +/** + * AppLegacySensitiveWordToolResponse + */ +export const zAppLegacySensitiveWordToolResponse = z.object({ + canned_response: z.string(), + enabled: z.boolean(), + words: z.array(z.string()), +}) + +/** + * AppLegacySensitiveWordToolResponseItem + */ +export const zAppLegacySensitiveWordToolResponseItem = z.object({ + 'sensitive-word-avoidance': zAppLegacySensitiveWordToolResponse, +}) + +/** + * AppAgentModeResponse + */ +export const zAppAgentModeResponse = z.object({ + enabled: z.boolean(), + max_iteration: z.int().optional(), + prompt: z.union([zAppAgentPromptResponse, z.string()]).nullish(), + strategy: z.union([zPlanningStrategy, z.enum(['cot', 'function-calling'])]).nullish(), + tools: z + .array( + z.union([ + zAppProviderAgentToolResponse, + zAppLegacyDatasetToolResponse, + zAppLegacyGoogleSearchToolResponse, + zAppLegacyWebReaderToolResponse, + zAppLegacyWikipediaToolResponse, + zAppLegacyCurrentDatetimeToolResponse, + zAppLegacySensitiveWordToolResponseItem, + ]), + ) + .optional(), +}) + +/** + * AppDatasetItemResponse + */ +export const zAppDatasetItemResponse = z.object({ + dataset: zAppDatasetReferenceResponse, +}) + +/** + * AppDatasetListResponse + */ +export const zAppDatasetListResponse = z.object({ + datasets: z.array(zAppDatasetItemResponse), + strategy: z.string().optional(), +}) + +/** + * AppMetadataConditionResponse + */ +export const zAppMetadataConditionResponse = z.object({ + comparison_operator: z.enum([ + '<', + '=', + '>', + 'after', + 'before', + 'contains', + 'empty', + 'end with', + 'in', + 'is', + 'is not', + 'not contains', + 'not empty', + 'not in', + 'start with', + '≠', + '≤', + '≥', + ]), + id: z.string().optional(), + metadata_id: z.string().optional(), + name: z.string(), + value: z.union([z.string(), z.array(z.string()), z.int(), z.number()]).nullish(), +}) + +/** + * AppMetadataFilteringConditionsResponse + */ +export const zAppMetadataFilteringConditionsResponse = z.object({ + conditions: z.array(zAppMetadataConditionResponse).nullish(), + logical_operator: z.enum(['and', 'or']).nullish(), +}) + +/** + * AppKeywordSettingResponse + */ +export const zAppKeywordSettingResponse = z.object({ + keyword_weight: z.number(), +}) + +/** + * AppVectorSettingResponse + */ +export const zAppVectorSettingResponse = z.object({ + embedding_model_name: z.string(), + embedding_provider_name: z.string(), + vector_weight: z.number(), +}) + +/** + * AppWeightsResponse + */ +export const zAppWeightsResponse = z.object({ + keyword_setting: zAppKeywordSettingResponse, + vector_setting: zAppVectorSettingResponse, + weight_type: z.enum(['customized', 'keyword_first', 'semantic_first']).optional(), +}) + +/** + * AppDatasetConfigsResponse + */ +export const zAppDatasetConfigsResponse = z.object({ + datasets: zAppDatasetListResponse.optional(), + metadata_filtering_conditions: zAppMetadataFilteringConditionsResponse.nullish(), + metadata_filtering_mode: z.enum(['automatic', 'disabled', 'manual']).optional(), + metadata_model_config: zAppModelSelectionResponse.nullish(), + reranking_enable: z.boolean().optional(), + reranking_enabled: z.boolean().optional(), + reranking_mode: zRerankMode.optional(), + reranking_model: zAppRerankingModelResponse.nullish(), + retrieval_model: z.enum(['multiple', 'single']), + score_threshold: z.number().nullish(), + score_threshold_enabled: z.boolean().optional(), + top_k: z.int().optional(), + weights: zAppWeightsResponse.nullish(), +}) + +/** + * AppModelConfigResponse + */ +export const zAppModelConfigResponse = z.object({ + agent_mode: zAppAgentModeResponse, + annotation_reply: z.union([ + zAppAnnotationReplyEnabledResponse, + zAppAnnotationReplyDisabledResponse, + ]), + chat_prompt_config: zAppChatPromptConfigResponse, + completion_prompt_config: zAppCompletionPromptConfigResponse, + created_at: z.int(), + created_by: z.string().nullable(), + dataset_configs: zAppDatasetConfigsResponse, + dataset_query_variable: z.string().nullable(), + external_data_tools: z.array( + z.union([zAppEnabledExternalDataToolResponse, zAppDisabledExternalDataToolResponse]), + ), + file_upload: zAppFileUploadResponse, + model: zAppModelSelectionResponse, + more_like_this: zAppEnabledConfigResponse, + opening_statement: z.string().nullable(), + pre_prompt: z.string().nullable(), + prompt_type: zPromptType, + retriever_resource: zAppEnabledConfigResponse, + sensitive_word_avoidance: zAppSensitiveWordAvoidanceResponse, + speech_to_text: zAppEnabledConfigResponse, + suggested_questions: z.array(z.string()), + suggested_questions_after_answer: zAppSuggestedQuestionsAfterAnswerResponse, + text_to_speech: zAppTextToSpeechResponse, + updated_at: z.int(), + updated_by: z.string().nullable(), + user_input_form: z.array( + z.union([ + zAppTextInputFormResponse, + zAppSelectFormResponse, + zAppParagraphFormResponse, + zAppNumberFormResponse, + zAppCheckboxFormResponse, + zAppFileFormResponse, + zAppFileListFormResponse, + zAppExternalDataToolFormResponse, + zAppJsonObjectFormResponse, + ]), + ), +}) + +/** + * AppDetailWithSite + */ +export const zAppDetailWithSite = z.object({ + access_mode: zWebAppAccessMode.nullable(), + api_base_url: z.string(), + app_id: z.string().nullable(), + bound_agent_id: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + deleted_tools: z.array(zDeletedTool), + description: z.string(), + enable_api: z.boolean(), + enable_site: z.boolean(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), + icon_url: z.string().nullable(), + id: z.string(), + maintainer: z.string().nullable(), + max_active_requests: z.int().nullable(), + mode: zAppMode, + model_config: zAppModelConfigResponse.nullable(), + name: z.string(), + permission_keys: z.array(z.string()), + site: zAppDetailSiteResponse.nullable(), + tags: z.array(zTag), + tracing: z.string().nullable(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), + workflow: zWorkflowPartial.nullable(), +}) + +/** + * AppDetail + */ +export const zAppDetail = z.object({ + access_mode: zWebAppAccessMode.nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + description: z.string(), + enable_api: z.boolean(), + enable_site: z.boolean(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + id: z.string(), + maintainer: z.string().nullable(), + mode: zAppMode, + model_config: zAppModelConfigResponse.nullable(), + name: z.string(), + permission_keys: z.array(z.string()), + tags: z.array(zTag), + tracing: z.string().nullable(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), + workflow: zWorkflowPartial.nullable(), +}) + /** * ButtonStyle * @@ -3332,21 +3877,6 @@ export const zUserActionConfig = z.object({ title: z.string().max(100), }) -/** - * FileType - */ -export const zFileType = z.enum(['audio', 'custom', 'document', 'image', 'video']) - -/** - * FileTransferMethod - */ -export const zFileTransferMethod = z.enum([ - 'datasource_file', - 'local_file', - 'remote_url', - 'tool_file', -]) - /** * FileInputConfig */ @@ -3542,21 +4072,6 @@ export const zAgentSoulDifyToolCredentialRef = z.object({ type: z.enum(['provider', 'tool']).optional().default('tool'), }) -/** - * ToolProviderType - * - * Enum class for tool provider - */ -export const zToolProviderType = z.enum([ - 'api', - 'app', - 'builtin', - 'dataset-retrieval', - 'mcp', - 'plugin', - 'workflow', -]) - /** * AgentSoulDifyToolConfig * @@ -4014,7 +4529,7 @@ export const zGeneratedAppResponseWritable = zJsonValue * AppPartial */ export const zAppPartialWritable = z.object({ - access_mode: z.string().nullish(), + access_mode: zWebAppAccessMode.nullish(), app_id: z.string().nullish(), author_name: z.string().nullish(), bound_agent_id: z.string().nullish(), @@ -4025,12 +4540,12 @@ export const zAppPartialWritable = z.object({ has_draft_trigger: z.boolean().nullish(), icon: z.string().nullish(), icon_background: z.string().nullish(), - icon_type: z.string().nullish(), + icon_type: zIconType.nullish(), id: z.string(), is_starred: z.boolean().optional().default(false), maintainer: z.string().nullish(), max_active_requests: z.int().nullish(), - mode: z.string(), + mode: zAppMode, model_config: zModelConfigPartial.nullish(), name: z.string(), permission_keys: z.array(z.string()).optional(), @@ -4056,63 +4571,63 @@ export const zAppPaginationWritable = z.object({ * AppDetailSiteResponse */ export const zAppDetailSiteResponseWritable = z.object({ - access_token: z.string().nullish(), - app_base_url: z.string().nullish(), - chat_color_theme: z.string().nullish(), - chat_color_theme_inverted: z.boolean().nullish(), - code: z.string().nullish(), - copyright: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - custom_disclaimer: z.string().nullish(), - customize_domain: z.string().nullish(), - customize_token_strategy: z.string().nullish(), - default_language: z.string().nullish(), - description: z.string().nullish(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.union([z.string(), zIconType]).nullish(), - input_placeholder: z.string().nullish(), - privacy_policy: z.string().nullish(), - prompt_public: z.boolean().nullish(), - show_workflow_steps: z.boolean().nullish(), - title: z.string().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), + access_token: z.string().nullable(), + app_base_url: z.string(), + chat_color_theme: z.string().nullable(), + chat_color_theme_inverted: z.boolean(), + code: z.string().nullable(), + copyright: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + custom_disclaimer: z.string(), + customize_domain: z.string().nullable(), + customize_token_strategy: zCustomizeTokenStrategy, + default_language: z.string(), + description: z.string().nullable(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), + input_placeholder: z.string().nullable(), + privacy_policy: z.string().nullable(), + prompt_public: z.boolean(), + show_workflow_steps: z.boolean(), + title: z.string(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), }) /** * AppDetailWithSite */ export const zAppDetailWithSiteWritable = z.object({ - access_mode: z.string().nullish(), - api_base_url: z.string().nullish(), - app_id: z.string().nullish(), - bound_agent_id: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - deleted_tools: z.array(zDeletedTool).optional(), - description: z.string().nullish(), + access_mode: zWebAppAccessMode.nullable(), + api_base_url: z.string(), + app_id: z.string().nullable(), + bound_agent_id: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + deleted_tools: z.array(zDeletedTool), + description: z.string(), enable_api: z.boolean(), enable_site: z.boolean(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), + icon: z.string().nullable(), + icon_background: z.string().nullable(), + icon_type: zIconType.nullable(), id: z.string(), - maintainer: z.string().nullish(), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zAppModelConfigResponse.nullish(), + maintainer: z.string().nullable(), + max_active_requests: z.int().nullable(), + mode: zAppMode, + model_config: zAppModelConfigResponse.nullable(), name: z.string(), - permission_keys: z.array(z.string()).optional(), - site: zAppDetailSiteResponseWritable.nullish(), - tags: z.array(zTag).optional(), - tracing: z.unknown().nullish(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), + permission_keys: z.array(z.string()), + site: zAppDetailSiteResponseWritable.nullable(), + tags: z.array(zTag), + tracing: z.string().nullable(), + updated_at: z.int(), + updated_by: z.string().nullable(), + use_icon_as_answer_icon: z.boolean(), + workflow: zWorkflowPartial.nullable(), }) /** diff --git a/packages/contracts/generated/api/console/snippets/types.gen.ts b/packages/contracts/generated/api/console/snippets/types.gen.ts index be473a7e1d3..57c28d99cbe 100644 --- a/packages/contracts/generated/api/console/snippets/types.gen.ts +++ b/packages/contracts/generated/api/console/snippets/types.gen.ts @@ -799,7 +799,7 @@ export type AgentSuggestedQuestionsAfterAnswerFeatureConfig = { } export type AgentTextToSpeechFeatureConfig = { - autoPlay?: string | null + autoPlay?: 'disabled' | 'enabled' | null enabled?: boolean language?: string | null voice?: string | null diff --git a/packages/contracts/generated/api/console/snippets/zod.gen.ts b/packages/contracts/generated/api/console/snippets/zod.gen.ts index c442c6ea565..520173ccdcc 100644 --- a/packages/contracts/generated/api/console/snippets/zod.gen.ts +++ b/packages/contracts/generated/api/console/snippets/zod.gen.ts @@ -760,7 +760,7 @@ export const zAgentFeatureToggleConfig = z.object({ * AgentTextToSpeechFeatureConfig */ export const zAgentTextToSpeechFeatureConfig = z.object({ - autoPlay: z.string().nullish(), + autoPlay: z.enum(['disabled', 'enabled']).nullish(), enabled: z.boolean().optional().default(false), language: z.string().nullish(), voice: z.string().nullish(), diff --git a/packages/contracts/generated/api/openapi/types.gen.ts b/packages/contracts/generated/api/openapi/types.gen.ts index b6242c85432..6d5aea59107 100644 --- a/packages/contracts/generated/api/openapi/types.gen.ts +++ b/packages/contracts/generated/api/openapi/types.gen.ts @@ -81,7 +81,7 @@ export type AppDslImportPayload = { export type AppDslImportResponse = { app_id?: string | null - app_mode?: string | null + app_mode?: AppMode | null current_dsl_version?: string error?: string hints?: Array @@ -305,7 +305,7 @@ export type HumanInputFormDefinitionResponse = { export type Import = { app_id?: string | null - app_mode?: string | null + app_mode?: AppMode | null current_dsl_version?: string error?: string id: string diff --git a/packages/contracts/generated/api/openapi/zod.gen.ts b/packages/contracts/generated/api/openapi/zod.gen.ts index 74078abe902..1a948478625 100644 --- a/packages/contracts/generated/api/openapi/zod.gen.ts +++ b/packages/contracts/generated/api/openapi/zod.gen.ts @@ -398,7 +398,7 @@ export const zImportStatus = z.enum(['completed', 'completed-with-warnings', 'fa */ export const zAppDslImportResponse = z.object({ app_id: z.string().nullish(), - app_mode: z.string().nullish(), + app_mode: zAppMode.nullish(), current_dsl_version: z.string().optional().default('0.7.0'), error: z.string().optional().default(''), hints: z.array(zHint).optional(), @@ -414,7 +414,7 @@ export const zAppDslImportResponse = z.object({ */ export const zImport = z.object({ app_id: z.string().nullish(), - app_mode: z.string().nullish(), + app_mode: zAppMode.nullish(), current_dsl_version: z.string().optional().default('0.7.0'), error: z.string().optional().default(''), id: z.string(), diff --git a/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts b/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts index d7913baba33..6a8bade05f7 100644 --- a/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts +++ b/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts @@ -198,7 +198,7 @@ describe('useAppInfoActions', () => { describe('onEdit', () => { it('should update app info and close modal on success', async () => { - const updatedApp = { ...mockAppDetail, name: 'Updated' } + const updatedApp = { ...mockAppDetail, name: 'Updated', max_active_requests: null } mockUpdateAppInfo.mockResolvedValue(updatedApp) const { result } = renderHook(() => useAppInfoActions({})) @@ -222,8 +222,8 @@ describe('useAppInfoActions', () => { expect.any(Function), ) const updateCachedApp = mockSetQueryData.mock.calls[0]![1] - expect(updateCachedApp({ id: 'app-1', name: 'Old name' })).toEqual( - expect.objectContaining({ id: 'app-1', name: 'Updated' }), + expect(updateCachedApp({ id: 'app-1', name: 'Old name', max_active_requests: 5 })).toEqual( + expect.objectContaining({ id: 'app-1', name: 'Updated', max_active_requests: null }), ) expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: consoleQuery.apps.get.key(), diff --git a/web/app/components/app-sidebar/app-info/use-app-info-actions.ts b/web/app/components/app-sidebar/app-info/use-app-info-actions.ts index bc5255b14da..4c2f43d594b 100644 --- a/web/app/components/app-sidebar/app-info/use-app-info-actions.ts +++ b/web/app/components/app-sidebar/app-info/use-app-info-actions.ts @@ -68,7 +68,10 @@ const updateCachedAppMetadata = (cachedApp: AppDetailWithSite | undefined, app: icon_background: app.icon_background, icon_type: app.icon_type, icon_url: app.icon_url, - max_active_requests: app.max_active_requests, + max_active_requests: + app.max_active_requests === undefined + ? cachedApp.max_active_requests + : app.max_active_requests, name: app.name, updated_at: app.updated_at, use_icon_as_answer_icon: app.use_icon_as_answer_icon, diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx index 003ae8c6535..cff81dec096 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/__tests__/index.spec.tsx @@ -646,8 +646,12 @@ describe('SettingsModal', () => { it('should disable save button while saving', async () => { // Arrange const user = userEvent.setup() + let finishSave!: (dataset: DataSet) => void mockUpdateDatasetSetting.mockImplementation( - () => new Promise((resolve) => setTimeout(resolve, 100)), + () => + new Promise((resolve) => { + finishSave = resolve + }), ) // Act @@ -658,6 +662,9 @@ describe('SettingsModal', () => { // Assert expect(saveButton).toBeDisabled() + + finishSave(createDataset()) + await waitFor(() => expect(saveButton).toBeEnabled()) }) it('should show error toast when save fails', async () => { diff --git a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx index de28eeb197d..776b6c8a1c6 100644 --- a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx +++ b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx @@ -4,6 +4,7 @@ import { consoleQuery } from '@/service/console' import { seedAccountProfileQuery } from '@/test/console/account-profile' import { createQueryClientWrapper } from '@/test/console/query-client' import { renderHook as renderHookWithConsoleState } from '@/test/console/render' +import { createAppDetailFixture } from '@/test/fixtures/app' import { createTestQueryClient } from '@/test/query-client' import { AppModeEnum, ModelModeType } from '@/types/app' import { AppACLPermission } from '@/utils/permission' @@ -272,14 +273,17 @@ describe('useConfiguration', () => { const detailQueryKey = consoleQuery.apps.byAppId.get.queryKey({ input: { params: { app_id: 'app-1' } }, }) - queryClient.setQueryData(detailQueryKey, { - enable_api: false, - enable_site: false, - icon_url: null, - id: 'app-1', - mode: 'chat', - name: 'Cached app', - }) + queryClient.setQueryData( + detailQueryKey, + createAppDetailFixture({ + enable_api: false, + enable_site: false, + icon_url: null, + id: 'app-1', + mode: 'chat', + name: 'Cached app', + }), + ) await waitFor(() => { expect(result.current.showLoading).toBe(false) diff --git a/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx b/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx index a45e7ecea8a..85d9ca7cb94 100644 --- a/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/switch-app-modal/__tests__/index.spec.tsx @@ -91,7 +91,7 @@ const createMockApp = (overrides: Partial = {}): AppPartial => ({ created_at: Date.now(), updated_at: Date.now(), tags: [], - access_mode: 'public_access', + access_mode: 'public', ...overrides, }) diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx index 532013fe8d8..e2cc079c024 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx @@ -1,5 +1,5 @@ 'use client' -import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen' +import type { AppPartial } from '@dify/contracts/api/console/apps/types.gen' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { useRef } from 'react' @@ -13,7 +13,7 @@ type Props = Readonly<{ app_id: string inputs: Record } - appDetail: Pick + appDetail: Pick onFormChange: (value: Record) => void }> diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/hooks/use-app-inputs-form-schema.ts b/web/app/components/plugins/plugin-detail-panel/app-selector/hooks/use-app-inputs-form-schema.ts index 7e4551792b6..41729e79b21 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/hooks/use-app-inputs-form-schema.ts +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/hooks/use-app-inputs-form-schema.ts @@ -1,6 +1,7 @@ 'use client' import type { AppDetailWithSite, + AppPartial, WorkflowResponse, } from '@dify/contracts/api/console/apps/types.gen' import type { FileUpload } from '@/app/components/base/features/types' @@ -162,7 +163,7 @@ function buildWorkflowSchema( } type UseAppInputsFormSchemaParams = { - appDetail: Pick + appDetail: Pick } type UseAppInputsFormSchemaResult = { diff --git a/web/features/agent-v2/agent-detail/__tests__/navigation.spec.tsx b/web/features/agent-v2/agent-detail/__tests__/navigation.spec.tsx index a893684cbf4..5ebc5365e84 100644 --- a/web/features/agent-v2/agent-detail/__tests__/navigation.spec.tsx +++ b/web/features/agent-v2/agent-detail/__tests__/navigation.spec.tsx @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { AgentPermission } from '@/features/agent-v2/acl' +import { createAgentFixture } from '@/test/fixtures/agent' import { AgentDetailSection, AgentDetailTop } from '../navigation' const mocks = vi.hoisted(() => ({ @@ -86,22 +87,23 @@ vi.mock('@/service/console', () => ({ }, })) -const createAgent = (overrides: Partial = {}): AgentAppDetailWithSite => ({ - permission_keys: Object.values(AgentPermission), - app_id: 'app-1', - description: 'Find and summarize market materials.', - enable_api: true, - enable_site: true, - icon: '🧪', - icon_background: '#E0F2FE', - icon_type: 'emoji', - id: 'agent-1', - icon_url: null, - mode: 'agent', - name: 'Research Agent', - role: 'Research Assistant', - ...overrides, -}) +const createAgent = (overrides: Partial = {}): AgentAppDetailWithSite => + createAgentFixture({ + permission_keys: Object.values(AgentPermission), + app_id: 'app-1', + description: 'Find and summarize market materials.', + enable_api: true, + enable_site: true, + icon: '🧪', + icon_background: '#E0F2FE', + icon_type: 'emoji', + id: 'agent-1', + icon_url: null, + mode: 'agent', + name: 'Research Agent', + role: 'Research Assistant', + ...overrides, + }) function renderAgentDetailSection(expand = true) { const queryClient = new QueryClient() diff --git a/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx b/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx index 4363f7e124f..1bb893babe0 100644 --- a/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx +++ b/web/features/agent-v2/agent-detail/access/components/__tests__/access-surface-cards.spec.tsx @@ -15,6 +15,8 @@ import { consoleQuery } from '@/service/console' import { seedAccountProfileQuery } from '@/test/console/account-profile' import { createQueryClientWrapper } from '@/test/console/query-client' import { seedFeatures, seedSystemFeatures } from '@/test/console/query-data' +import { createAgentFixture } from '@/test/fixtures/agent' +import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app' import { ServiceApiAccessCard } from '../service-api-access-card' import { WebAppAccessCard } from '../web-app-access-card' @@ -230,7 +232,7 @@ vi.mock('@/service/console', () => ({ })) function createAgent(overrides: Partial = {}): AgentAppDetailWithSite { - return { + return createAgentFixture({ permission_keys: Object.values(AgentPermission), access_ready: true, enable_api: true, @@ -243,32 +245,23 @@ function createAgent(overrides: Partial = {}): AgentAppD backing_app_id: 'app-1', api_base_url: 'https://api.example.test/v1', access_mode: 'sso_verified', - site: { - access_token: 'site-token', + site: createAppSiteFixture({ app_base_url: 'https://chat.example.test', - chat_color_theme_inverted: false, - default_language: 'en-US', - icon_url: null, - show_workflow_steps: false, title: 'Support Agent', - use_icon_as_answer_icon: false, - } as NonNullable & { - access_token: string - app_base_url: string - }, + }), ...overrides, - } + }) } function createAppDetailResponse(overrides: Partial = {}): AppDetail { - return { + return createAppDetailFixture({ enable_api: true, enable_site: true, id: 'app-1', mode: 'agent', name: 'Support Agent', ...overrides, - } + }) } function createAgentApiAccessResponse( @@ -624,6 +617,35 @@ describe('Agent access surface cards', () => { expect(toast.error).toHaveBeenCalledWith('common.actionMsg.modifiedUnsuccessfully') }) + it('should replace the Web App URL token without discarding the cached site settings', async () => { + const user = userEvent.setup() + const agent = createAgent() + const queryClient = renderWithQueryClient( + , + ) + queryClient.setQueryData(['agent-detail', 'agent-1'], agent) + mocks.siteAccessTokenResetMutation.mockResolvedValueOnce( + createAppSiteFixture({ code: 'new-site-token' }), + ) + + await user.click( + screen.getByRole('button', { name: 'agentV2.agentDetail.access.webApp.refreshUrl' }), + ) + + expect(mocks.siteAccessTokenResetMutation.mock.calls[0]?.[0]).toEqual({ + params: { app_id: 'app-1' }, + }) + await waitFor(() => { + expect( + queryClient.getQueryData(['agent-detail', 'agent-1'])?.site, + ).toMatchObject({ + code: 'new-site-token', + access_token: 'new-site-token', + title: 'Support Agent', + }) + }) + }) + it('should open the customize dialog with the backing app id and API base URL', async () => { const user = userEvent.setup() @@ -707,7 +729,7 @@ describe('Agent access surface cards', () => { }) }) - it('should save settings through the backing app id and update the agent detail cache', async () => { + it('should save settings through the backing app id and invalidate agent detail', async () => { const user = userEvent.setup() mocks.getUserCanAccess.mockResolvedValue({ result: false }) const agent = createAgent({ @@ -781,18 +803,9 @@ describe('Agent access surface cards', () => { }) }) expect(mocks.siteMutation.mock.calls[0]?.[0].body).not.toHaveProperty('enable_sso') - expect( - queryClient.getQueryData(['agent-detail', 'agent-1']), - ).toMatchObject({ - site: { - access_token: 'new-site-token', - chat_color_theme: '#123456', - description: 'Updated web description.', - icon_url: null, - title: 'Support Portal', - }, + await waitFor(() => { + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['agent-detail', 'agent-1'] }) }) - expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['agent-detail', 'agent-1'] }) }) it('should fall back to the Agent icon tuple when WebApp site icon data is missing', async () => { @@ -918,10 +931,10 @@ describe('Agent access surface cards', () => { ).toBeDisabled() }) - it('should keep customize disabled until the generated contract provides the required fields', () => { + it('should keep customize disabled when the app base URL is empty', () => { renderWithQueryClient( , @@ -1246,22 +1259,19 @@ describe('Agent access surface cards', () => { expect(screen.getByRole('button', { name: organizationAccessLabel })).toBeDisabled() }) - it.each([null, 'future-access-mode'])( - 'should hide the access mode entry when the access mode is %s', - (accessMode) => { - renderWithQueryClient( - , - ) + it('should hide the access mode entry when the access mode is unavailable', () => { + renderWithQueryClient( + , + ) - expect( - screen.queryByRole('button', { name: organizationAccessLabel }), - ).not.toBeInTheDocument() - }, - ) + expect( + screen.queryByRole('button', { name: organizationAccessLabel }), + ).not.toBeInTheDocument() + }) it('should open the shared App access control dialog for the backing app', async () => { const user = userEvent.setup() diff --git a/web/features/agent-v2/agent-detail/access/components/web-app-access-card.tsx b/web/features/agent-v2/agent-detail/access/components/web-app-access-card.tsx index d89ccb1f818..1de5c3b07d9 100644 --- a/web/features/agent-v2/agent-detail/access/components/web-app-access-card.tsx +++ b/web/features/agent-v2/agent-detail/access/components/web-app-access-card.tsx @@ -117,8 +117,8 @@ export function WebAppAccessCard({ ...agentDetail, site: { ...agentDetail.site, - ...site, - access_token: site.code, + code: site.code ?? agentDetail.site.code, + access_token: site.code ?? agentDetail.site.access_token, }, } }, @@ -191,39 +191,12 @@ export function WebAppAccessCard({ const sitePayload = body satisfies AppSiteUpdatePayload try { - const updatedSite = await updateSiteMutation.mutateAsync({ + await updateSiteMutation.mutateAsync({ params: { app_id: appId, }, body: sitePayload, }) - - queryClient.setQueryData( - agentDetailQueryKey, - (agentDetail) => - agentDetail - ? { - ...agentDetail, - site: { - ...agentDetail.site, - ...updatedSite, - ...sitePayload, - access_token: - updatedSite.code ?? - agentDetail.site?.access_token ?? - agentDetail.site?.code ?? - null, - code: - updatedSite.code ?? - agentDetail.site?.code ?? - agentDetail.site?.access_token ?? - null, - app_base_url: agentDetail.site?.app_base_url ?? site?.app_base_url ?? null, - icon_url: null, - }, - } - : agentDetail, - ) await queryClient.invalidateQueries({ queryKey: agentDetailQueryKey }) toast.success(tCommon(($) => $['actionMsg.modifiedSuccessfully'])) } catch { diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.browser.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.browser.spec.tsx index 9563ca628ae..d541d2c9a5d 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.browser.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.browser.spec.tsx @@ -9,6 +9,7 @@ import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { consoleQuery } from '@/service/console' import { createSystemFeaturesFixture } from '@/test/console/system-features' import { createAgentFixture } from '@/test/fixtures/agent' +import { createAppSiteFixture } from '@/test/fixtures/app' import { createTestQueryClient } from '@/test/query-client' import { AgentConfigurePublishBar } from '../publish-bar' @@ -49,11 +50,10 @@ async function setup() { createAgentFixture({ access_ready: true, enable_site: true, - site: { + site: createAppSiteFixture({ access_token: 'published-token', app_base_url: 'https://apps.example.test', - icon_url: null, - }, + }), }), ) queryClient.setQueryData( diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx index 0fd9743f2b5..962a408488e 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx @@ -17,6 +17,7 @@ import { import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { consoleQuery } from '@/service/console' +import { createAppSiteFixture } from '@/test/fixtures/app' import { createNuqsTestWrapper } from '@/test/nuqs-testing' import { AgentConfigurePublishBar } from '../publish-bar' @@ -1140,11 +1141,10 @@ describe('AgentConfigurePublishBar', () => { const readyAgent = { access_ready: true, enable_site: true, - site: { + site: createAppSiteFixture({ access_token: 'published-token', app_base_url: 'https://apps.example.test', - icon_url: null, - }, + }), } it.each([ diff --git a/web/service/console/client.spec.ts b/web/service/console/client.spec.ts index e64ea69fd0f..940fdd817c4 100644 --- a/web/service/console/client.spec.ts +++ b/web/service/console/client.spec.ts @@ -6,6 +6,8 @@ import type { MutationFunctionContext, QueryFunctionContext } from '@tanstack/re import type { consoleQuery as ConsoleQuery } from '@/service/console' import { MutationObserver, QueryClient, QueryObserver } from '@tanstack/react-query' import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test' +import { createAgentFixture } from '@/test/fixtures/agent' +import { createAppDetailFixture } from '@/test/fixtures/app' import { normalizeConsoleOpenAPIURL } from './openapi-url' const loadConsoleQuery = async () => { @@ -84,22 +86,8 @@ const getRetryFn = (queryOptions: object): RetryFn => { return retry as RetryFn } -const createAgent = (overrides: Partial = {}): AgentMutationResponse => ({ - ...overrides, - access_ready: overrides.access_ready ?? true, - debug_conversation_has_messages: overrides.debug_conversation_has_messages ?? false, - debug_conversation_message_count: overrides.debug_conversation_message_count ?? 0, - enable_api: overrides.enable_api ?? true, - enable_site: overrides.enable_site ?? true, - description: overrides.description ?? 'Agent description', - hidden_app_backed: overrides.hidden_app_backed ?? false, - id: overrides.id ?? 'agent-1', - icon_url: overrides.icon_url ?? null, - mode: overrides.mode ?? 'agent', - name: overrides.name ?? 'Agent', - permission_keys: overrides.permission_keys ?? [], - role: overrides.role ?? 'Assistant', -}) +const createAgent = (overrides: Partial = {}): AgentMutationResponse => + createAgentFixture({ role: 'Assistant', ...overrides }) const createComposerState = ( overrides: Partial = {}, @@ -645,13 +633,7 @@ describe('consoleQuery app mutation defaults', () => { .spyOn(queryClient, 'invalidateQueries') .mockImplementation(() => new Promise(() => {})) const context = createMutationContext(queryClient) - const appDetail: AppDetail = { - enable_api: true, - enable_site: true, - id: 'app-1', - mode: 'chat', - name: 'App', - } + const appDetail: AppDetail = createAppDetailFixture() const appSite: AppSiteResponse = { app_id: 'app-1', customize_token_strategy: 'fixed', @@ -723,14 +705,11 @@ describe('consoleQuery app mutation defaults', () => { const otherDetailQueryKey = consoleQuery.apps.byAppId.get.queryKey({ input: { params: { app_id: 'app-2' } }, }) - const updatedApp = { + const updatedApp = createAppDetailFixture({ enable_api: false, enable_site: false, - icon_url: null, - id: 'app-1', - mode: 'chat', name: 'Updated app', - } + }) queryClient.setQueryData(detailQueryKey, { ...updatedApp, name: 'Old app' }) queryClient.setQueryData(otherDetailQueryKey, { ...updatedApp, id: 'app-2', name: 'Other app' }) diff --git a/web/test/fixtures/agent.ts b/web/test/fixtures/agent.ts index d510f6b660f..be1e59869eb 100644 --- a/web/test/fixtures/agent.ts +++ b/web/test/fixtures/agent.ts @@ -1,9 +1,11 @@ import type { AgentAppDetailWithSite } from '@dify/contracts/api/console/agent/types.gen' import { zAgentAppDetailWithSite } from '@dify/contracts/api/console/agent/zod.gen' import { AgentPermission } from '@/features/agent-v2/acl' +import { createAppDetailFixture } from './app' export function createAgentFixture(overrides: Partial = {}) { return zAgentAppDetailWithSite.parse({ + ...createAppDetailFixture(), id: 'agent-1', app_id: 'app-1', name: 'Agent', diff --git a/web/test/fixtures/app.ts b/web/test/fixtures/app.ts new file mode 100644 index 00000000000..e53f02c530c --- /dev/null +++ b/web/test/fixtures/app.ts @@ -0,0 +1,73 @@ +import type { + AppDetailSiteResponse, + AppDetailWithSite, +} from '@dify/contracts/api/console/apps/types.gen' + +export function createAppSiteFixture( + overrides: Partial = {}, +): AppDetailSiteResponse { + return { + access_token: 'site-token', + app_base_url: 'https://apps.example.test', + chat_color_theme: null, + chat_color_theme_inverted: false, + code: 'site-token', + copyright: null, + created_at: 1, + created_by: null, + custom_disclaimer: '', + customize_domain: null, + customize_token_strategy: 'not_allow', + default_language: 'en-US', + description: null, + icon: null, + icon_background: null, + icon_type: null, + icon_url: null, + input_placeholder: null, + privacy_policy: null, + prompt_public: false, + show_workflow_steps: false, + title: 'App', + updated_at: 1, + updated_by: null, + use_icon_as_answer_icon: false, + ...overrides, + } +} + +export function createAppDetailFixture( + overrides: Partial = {}, +): AppDetailWithSite { + return { + access_mode: null, + api_base_url: 'https://api.example.test/v1', + app_id: null, + bound_agent_id: null, + created_at: 1, + created_by: null, + deleted_tools: [], + description: '', + enable_api: true, + enable_site: true, + icon: null, + icon_background: null, + icon_type: null, + icon_url: null, + id: 'app-1', + maintainer: null, + max_active_requests: null, + mode: 'chat', + model_config: null, + name: 'App', + permission_keys: [], + site: null, + tags: [], + tracing: null, + updated_at: 1, + updated_by: null, + use_icon_as_answer_icon: false, + workflow: null, + ...overrides, + } +}