diff --git a/api/commands/plugin.py b/api/commands/plugin.py index 71c19f842fb..445ecb31415 100644 --- a/api/commands/plugin.py +++ b/api/commands/plugin.py @@ -16,7 +16,7 @@ from core.plugin.plugin_service import PluginService from core.tools.utils.system_encryption import encrypt_system_params from extensions.ext_database import db from models import Tenant -from models.account import TenantPluginAutoUpgradeStrategy +from models.account import TenantPluginAutoUpgradeCategory, TenantPluginAutoUpgradeStrategy from models.oauth import DatasourceOauthParamConfig, DatasourceProvider from models.provider_ids import DatasourceProviderID, ToolProviderID from models.source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding @@ -406,7 +406,7 @@ def migrate_data_for_plugin(): def _candidate_auto_upgrade_strategy_tenant_ids_stmt(limit: int | None = None): - category_count = len(TenantPluginAutoUpgradeStrategy.PluginCategory) + category_count = len(TenantPluginAutoUpgradeCategory) stmt = ( select(TenantPluginAutoUpgradeStrategy.tenant_id) .group_by(TenantPluginAutoUpgradeStrategy.tenant_id) diff --git a/api/controllers/console/workspace/__init__.py b/api/controllers/console/workspace/__init__.py index 13bc98c8047..09bb535e626 100644 --- a/api/controllers/console/workspace/__init__.py +++ b/api/controllers/console/workspace/__init__.py @@ -8,7 +8,7 @@ from werkzeug.exceptions import Forbidden from configs import dify_config from extensions.ext_database import db from libs.login import current_account_with_tenant -from models.account import TenantPluginPermission +from models.account import TenantPluginDebugPermission, TenantPluginInstallPermission, TenantPluginPermission def plugin_permission_required( @@ -40,22 +40,22 @@ def plugin_permission_required( if install_required: match permission.install_permission: - case TenantPluginPermission.InstallPermission.NOBODY: + case TenantPluginInstallPermission.NOBODY: raise Forbidden() - case TenantPluginPermission.InstallPermission.ADMINS: + case TenantPluginInstallPermission.ADMINS: if not user.is_admin_or_owner: raise Forbidden() - case TenantPluginPermission.InstallPermission.EVERYONE: + case TenantPluginInstallPermission.EVERYONE: pass if debug_required: match permission.debug_permission: - case TenantPluginPermission.DebugPermission.NOBODY: + case TenantPluginDebugPermission.NOBODY: raise Forbidden() - case TenantPluginPermission.DebugPermission.ADMINS: + case TenantPluginDebugPermission.ADMINS: if not user.is_admin_or_owner: raise Forbidden() - case TenantPluginPermission.DebugPermission.EVERYONE: + case TenantPluginDebugPermission.EVERYONE: pass return view(*args, **kwargs) diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index c13c8aa162f..2f4ef1c5b42 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -1,12 +1,13 @@ from __future__ import annotations from datetime import datetime -from typing import Any, Literal +from http import HTTPStatus +from typing import Literal import pytz from flask import request from flask_restx import Resource -from pydantic import BaseModel, Field, RootModel, field_validator, model_validator +from pydantic import BaseModel, Field, field_validator, model_validator from sqlalchemy import select from werkzeug.exceptions import NotFound @@ -47,7 +48,7 @@ from controllers.console.wraps import ( ) from extensions.ext_database import db from fields.base import ResponseModel -from fields.member_fields import Account as AccountResponse +from fields.member_fields import AccountResponse from graphon.file import helpers as file_helpers from libs.datetime_utils import naive_utc_now from libs.helper import EmailStr, dump_response, extract_remote_ip, timezone, to_timestamp @@ -194,10 +195,6 @@ register_schema_models( ) -def _serialize_account(account) -> dict[str, Any]: - return AccountResponse.model_validate(account, from_attributes=True).model_dump(mode="json") - - class AccountIntegrateResponse(ResponseModel): provider: str created_at: int | None = None @@ -236,23 +233,15 @@ class EducationAutocompleteResponse(ResponseModel): has_next: bool | None = None -class EducationActivateResponse(RootModel[dict[str, Any]]): - root: dict[str, Any] - - -register_schema_models( - console_ns, - AccountIntegrateResponse, - AccountIntegrateListResponse, - EducationVerifyResponse, - EducationStatusResponse, - EducationAutocompleteResponse, -) register_response_schema_models( console_ns, AccountResponse, + AccountIntegrateResponse, + AccountIntegrateListResponse, AvatarUrlResponse, - EducationActivateResponse, + EducationVerifyResponse, + EducationStatusResponse, + EducationAutocompleteResponse, SimpleResultDataResponse, SimpleResultResponse, VerificationTokenResponse, @@ -262,7 +251,7 @@ register_response_schema_models( @console_ns.route("/account/init") class AccountInitApi(Resource): @console_ns.expect(console_ns.models[AccountInitPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultResponse.__name__]) @setup_required @login_required @with_current_user @@ -302,7 +291,7 @@ class AccountInitApi(Resource): account.initialized_at = naive_utc_now() db.session.commit() - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") @console_ns.route("/account/profile") @@ -310,11 +299,11 @@ class AccountProfileApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @enterprise_license_required @with_current_user def get(self, current_user: Account): - return _serialize_account(current_user) + return dump_response(AccountResponse, current_user) @console_ns.route("/account/name") @@ -323,14 +312,14 @@ class AccountNameApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} args = AccountNamePayload.model_validate(payload) updated_account = AccountService.update_account(current_user, session=db.session, name=args.name) - return _serialize_account(updated_account) + return dump_response(AccountResponse, updated_account) @console_ns.route("/account/avatar") @@ -338,7 +327,7 @@ class AccountAvatarApi(Resource): @console_ns.doc("get_account_avatar") @console_ns.doc(description="Get account avatar url") @console_ns.doc(params=query_params_from_model(AccountAvatarQuery)) - @console_ns.response(200, "Success", console_ns.models[AvatarUrlResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AvatarUrlResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -349,7 +338,7 @@ class AccountAvatarApi(Resource): avatar = args.avatar if avatar.startswith(("http://", "https://")): - return dump_response(AvatarUrlResponse, {"avatar_url": avatar}) + return AvatarUrlResponse(avatar_url=avatar).model_dump(mode="json") upload_file = db.session.scalar(select(UploadFile).where(UploadFile.id == avatar).limit(1)) if upload_file is None: @@ -362,13 +351,13 @@ class AccountAvatarApi(Resource): raise NotFound("Avatar file not found") avatar_url = file_helpers.get_signed_file_url(upload_file_id=upload_file.id) - return dump_response(AvatarUrlResponse, {"avatar_url": avatar_url}) + return AvatarUrlResponse(avatar_url=avatar_url).model_dump(mode="json") @console_ns.expect(console_ns.models[AccountAvatarPayload.__name__]) @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} @@ -376,7 +365,7 @@ class AccountAvatarApi(Resource): updated_account = AccountService.update_account(current_user, session=db.session, avatar=args.avatar) - return _serialize_account(updated_account) + return dump_response(AccountResponse, updated_account) @console_ns.route("/account/interface-language") @@ -385,7 +374,7 @@ class AccountInterfaceLanguageApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} @@ -395,7 +384,7 @@ class AccountInterfaceLanguageApi(Resource): current_user, session=db.session, interface_language=args.interface_language ) - return _serialize_account(updated_account) + return dump_response(AccountResponse, updated_account) @console_ns.route("/account/interface-theme") @@ -404,7 +393,7 @@ class AccountInterfaceThemeApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} @@ -414,7 +403,7 @@ class AccountInterfaceThemeApi(Resource): current_user, session=db.session, interface_theme=args.interface_theme ) - return _serialize_account(updated_account) + return dump_response(AccountResponse, updated_account) @console_ns.route("/account/timezone") @@ -423,7 +412,7 @@ class AccountTimezoneApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} @@ -431,7 +420,7 @@ class AccountTimezoneApi(Resource): updated_account = AccountService.update_account(current_user, session=db.session, timezone=args.timezone) - return _serialize_account(updated_account) + return dump_response(AccountResponse, updated_account) @console_ns.route("/account/password") @@ -440,7 +429,7 @@ class AccountPasswordApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} @@ -452,7 +441,7 @@ class AccountPasswordApi(Resource): except ServiceCurrentPasswordIncorrectError: raise CurrentPasswordIncorrectError() - return _serialize_account(current_user) + return dump_response(AccountResponse, current_user) @console_ns.route("/account/integrates") @@ -460,7 +449,7 @@ class AccountIntegrateApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountIntegrateListResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountIntegrateListResponse.__name__]) @with_current_user def get(self, account: Account): account_integrates = db.session.scalars( @@ -471,33 +460,29 @@ class AccountIntegrateApi(Resource): oauth_base_path = "/console/api/oauth/login" providers = ["github", "google"] - integrate_data = [] + integrate_data: list[AccountIntegrateResponse] = [] for provider in providers: existing_integrate = next((ai for ai in account_integrates if ai.provider == provider), None) if existing_integrate: integrate_data.append( - { - "id": existing_integrate.id, - "provider": provider, - "created_at": existing_integrate.created_at, - "is_bound": True, - "link": None, - } + AccountIntegrateResponse( + provider=provider, + created_at=to_timestamp(existing_integrate.created_at), + is_bound=True, + link=None, + ) ) else: integrate_data.append( - { - "id": None, - "provider": provider, - "created_at": None, - "is_bound": False, - "link": f"{base_url}{oauth_base_path}/{provider}", - } + AccountIntegrateResponse( + provider=provider, + created_at=None, + is_bound=False, + link=f"{base_url}{oauth_base_path}/{provider}", + ) ) - return AccountIntegrateListResponse( - data=[AccountIntegrateResponse.model_validate(item) for item in integrate_data] - ).model_dump(mode="json") + return AccountIntegrateListResponse(data=integrate_data).model_dump(mode="json") @console_ns.route("/account/delete/verify") @@ -505,19 +490,19 @@ class AccountDeleteVerifyApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[SimpleResultDataResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultDataResponse.__name__]) @with_current_user def get(self, account: Account): token, code = AccountService.generate_account_deletion_verification_code(account) AccountService.send_account_deletion_verification_email(account, code) - return {"result": "success", "data": token} + return SimpleResultDataResponse(result="success", data=token).model_dump(mode="json") @console_ns.route("/account/delete") class AccountDeleteApi(Resource): @console_ns.expect(console_ns.models[AccountDeletePayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -531,13 +516,13 @@ class AccountDeleteApi(Resource): AccountService.delete_account(account) - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") @console_ns.route("/account/delete/feedback") class AccountDeleteUpdateFeedbackApi(Resource): @console_ns.expect(console_ns.models[AccountDeletionFeedbackPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultResponse.__name__]) @setup_required def post(self): payload = console_ns.payload or {} @@ -545,7 +530,7 @@ class AccountDeleteUpdateFeedbackApi(Resource): BillingService.update_account_deletion_feedback(args.email, args.feedback) - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") @console_ns.route("/account/education/verify") @@ -555,18 +540,19 @@ class EducationVerifyApi(Resource): @account_initialization_required @only_edition_cloud @cloud_edition_billing_enabled - @console_ns.response(200, "Success", console_ns.models[EducationVerifyResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[EducationVerifyResponse.__name__]) @with_current_user def get(self, account: Account): - return EducationVerifyResponse.model_validate( - BillingService.EducationIdentity.verify(account.id, account.email) or {} - ).model_dump(mode="json") + return dump_response( + EducationVerifyResponse, BillingService.EducationIdentity.verify(account.id, account.email) or {} + ) @console_ns.route("/account/education") class EducationApi(Resource): @console_ns.expect(console_ns.models[EducationActivatePayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[EducationActivateResponse.__name__]) + # response-contract:ignore billing-service activation payload; TODO: model education activation result. + @console_ns.response(HTTPStatus.OK, "Success") @setup_required @login_required @account_initialization_required @@ -577,21 +563,22 @@ class EducationApi(Resource): payload = console_ns.payload or {} args = EducationActivatePayload.model_validate(payload) - return BillingService.EducationIdentity.activate(account, args.token, args.institution, args.role) + result = BillingService.EducationIdentity.activate(account, args.token, args.institution, args.role) + return result @setup_required @login_required @account_initialization_required @only_edition_cloud @cloud_edition_billing_enabled - @console_ns.response(200, "Success", console_ns.models[EducationStatusResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[EducationStatusResponse.__name__]) @with_current_user def get(self, account: Account): res = BillingService.EducationIdentity.status(account.id) or {} # convert expire_at to UTC timestamp from isoformat if res and "expire_at" in res: res["expire_at"] = datetime.fromisoformat(res["expire_at"]).astimezone(pytz.utc) - return EducationStatusResponse.model_validate(res).model_dump(mode="json") + return dump_response(EducationStatusResponse, res) @console_ns.route("/account/education/autocomplete") @@ -602,20 +589,21 @@ class EducationAutoCompleteApi(Resource): @account_initialization_required @only_edition_cloud @cloud_edition_billing_enabled - @console_ns.response(200, "Success", console_ns.models[EducationAutocompleteResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[EducationAutocompleteResponse.__name__]) def get(self): payload = request.args.to_dict(flat=True) args = EducationAutocompleteQuery.model_validate(payload) - return EducationAutocompleteResponse.model_validate( - BillingService.EducationIdentity.autocomplete(args.keywords, args.page, args.limit) or {} - ).model_dump(mode="json") + return dump_response( + EducationAutocompleteResponse, + BillingService.EducationIdentity.autocomplete(args.keywords, args.page, args.limit) or {}, + ) @console_ns.route("/account/change-email") class ChangeEmailSendEmailApi(Resource): @console_ns.expect(console_ns.models[ChangeEmailSendPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultDataResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultDataResponse.__name__]) @enable_change_email @setup_required @login_required @@ -669,13 +657,13 @@ class ChangeEmailSendEmailApi(Resource): language=language, phase=send_phase, ) - return {"result": "success", "data": token} + return SimpleResultDataResponse(result="success", data=token).model_dump(mode="json") @console_ns.route("/account/change-email/validity") class ChangeEmailCheckApi(Resource): @console_ns.expect(console_ns.models[ChangeEmailValidityPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[VerificationTokenResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[VerificationTokenResponse.__name__]) @enable_change_email @setup_required @login_required @@ -716,7 +704,9 @@ class ChangeEmailCheckApi(Resource): new_token = AccountService.generate_change_email_token(refreshed_token_data, current_user) AccountService.reset_change_email_error_rate_limit(user_email) - return {"is_valid": True, "email": normalized_token_email, "token": new_token} + return VerificationTokenResponse(is_valid=True, email=normalized_token_email, token=new_token).model_dump( + mode="json" + ) @console_ns.route("/account/change-email/reset") @@ -726,7 +716,7 @@ class ChangeEmailResetApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountResponse.__name__]) @with_current_user def post(self, current_user: Account): payload = console_ns.payload or {} @@ -768,13 +758,13 @@ class ChangeEmailResetApi(Resource): email=normalized_new_email, ) - return _serialize_account(updated_account) + return dump_response(AccountResponse, updated_account) @console_ns.route("/account/change-email/check-email-unique") class CheckEmailUnique(Resource): @console_ns.expect(console_ns.models[CheckEmailUniquePayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultResponse.__name__]) @setup_required def post(self): payload = console_ns.payload or {} @@ -784,4 +774,4 @@ class CheckEmailUnique(Resource): raise AccountInFreezeError() if not AccountService.check_email_unique(normalized_email, session=db.session): raise EmailAlreadyInUseError() - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") diff --git a/api/controllers/console/workspace/endpoint.py b/api/controllers/console/workspace/endpoint.py index ddb0f7045d9..781b4b6c1a2 100644 --- a/api/controllers/console/workspace/endpoint.py +++ b/api/controllers/console/workspace/endpoint.py @@ -6,13 +6,17 @@ verb-based aliases stay available as deprecated resources so OpenAPI metadata marks only the legacy paths as deprecated. """ +from datetime import datetime +from enum import StrEnum +from http import HTTPStatus from typing import Any from flask import request from flask_restx import Resource from pydantic import BaseModel, Field -from controllers.common.schema import query_params_from_model, register_schema_models +from controllers.common.fields import SuccessResponse +from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.wraps import ( RBACPermission, @@ -24,8 +28,14 @@ from controllers.console.wraps import ( with_current_tenant_id, with_current_user_id, ) +from core.entities.parameter_entities import ( + AppSelectorScope, + ModelSelectorScope, + ToolSelectorScope, +) +from core.entities.provider_entities import ProviderConfigType from core.plugin.impl.exc import PluginPermissionDeniedError -from graphon.model_runtime.utils.encoders import jsonable_encoder +from fields.base import ResponseModel from libs.login import login_required from services.plugin.endpoint_service import EndpointService @@ -40,14 +50,17 @@ class EndpointIdPayload(BaseModel): endpoint_id: str -class EndpointUpdatePayload(BaseModel): +class EndpointSettingsPayload(BaseModel): settings: dict[str, Any] name: str = Field(min_length=1) -class LegacyEndpointUpdatePayload(EndpointIdPayload): - settings: dict[str, Any] - name: str = Field(min_length=1) +class EndpointUpdatePayload(EndpointSettingsPayload): + pass + + +class LegacyEndpointUpdatePayload(EndpointIdPayload, EndpointSettingsPayload): + pass class EndpointListQuery(BaseModel): @@ -59,98 +72,158 @@ class EndpointListForPluginQuery(EndpointListQuery): plugin_id: str -class EndpointCreateResponse(BaseModel): - success: bool = Field(description="Operation success") +class EndpointProviderConfigScope(StrEnum): + ALL = AppSelectorScope.ALL.value + CHAT = AppSelectorScope.CHAT.value + WORKFLOW = AppSelectorScope.WORKFLOW.value + COMPLETION = AppSelectorScope.COMPLETION.value + LLM = ModelSelectorScope.LLM.value + TEXT_EMBEDDING = ModelSelectorScope.TEXT_EMBEDDING.value + RERANK = ModelSelectorScope.RERANK.value + TTS = ModelSelectorScope.TTS.value + SPEECH2TEXT = ModelSelectorScope.SPEECH2TEXT.value + MODERATION = ModelSelectorScope.MODERATION.value + VISION = ModelSelectorScope.VISION.value + CUSTOM = ToolSelectorScope.CUSTOM.value + BUILTIN = ToolSelectorScope.BUILTIN.value -class EndpointListResponse(BaseModel): - endpoints: list[dict[str, Any]] = Field( - description="Endpoint information", - ) +class EndpointProviderConfigI18nResponse(ResponseModel): + en_US: str + zh_Hans: str | None = None + pt_BR: str | None = None + ja_JP: str | None = None -class PluginEndpointListResponse(BaseModel): - endpoints: list[dict[str, Any]] = Field( - description="Endpoint information", - ) +class EndpointProviderConfigOptionResponse(ResponseModel): + value: str + label: EndpointProviderConfigI18nResponse -class EndpointDeleteResponse(BaseModel): - success: bool = Field(description="Operation success") +class EndpointProviderConfigResponse(ResponseModel): + type: ProviderConfigType + name: str + scope: EndpointProviderConfigScope | None = None + required: bool = False + default: int | str | float | bool | None = None + options: list[EndpointProviderConfigOptionResponse] | None = None + multiple: bool = False + label: EndpointProviderConfigI18nResponse | None = None + help: EndpointProviderConfigI18nResponse | None = None + url: str | None = None + placeholder: EndpointProviderConfigI18nResponse | None = None -class EndpointUpdateResponse(BaseModel): - success: bool = Field(description="Operation success") +class EndpointDeclarationResponse(ResponseModel): + path: str + method: str + hidden: bool = False -class EndpointEnableResponse(BaseModel): - success: bool = Field(description="Operation success") +class EndpointProviderDeclarationResponse(ResponseModel): + settings: list[EndpointProviderConfigResponse] = Field(default_factory=list) + endpoints: list[EndpointDeclarationResponse] | None = Field(default_factory=list) -class EndpointDisableResponse(BaseModel): - success: bool = Field(description="Operation success") +class EndpointListItemResponse(ResponseModel): + id: str + created_at: datetime + updated_at: datetime + tenant_id: str + plugin_id: str + settings: dict[str, Any] + expired_at: datetime + declaration: EndpointProviderDeclarationResponse = Field(default_factory=EndpointProviderDeclarationResponse) + name: str + enabled: bool + url: str + hook_id: str + + +class EndpointListResponse(ResponseModel): + endpoints: list[EndpointListItemResponse] = Field(description="Endpoint information") register_schema_models( console_ns, EndpointCreatePayload, EndpointIdPayload, + EndpointSettingsPayload, EndpointUpdatePayload, LegacyEndpointUpdatePayload, EndpointListQuery, EndpointListForPluginQuery, - EndpointCreateResponse, +) +register_response_schema_models( + console_ns, + SuccessResponse, + EndpointProviderConfigOptionResponse, + EndpointProviderConfigResponse, + EndpointDeclarationResponse, + EndpointProviderDeclarationResponse, + EndpointListItemResponse, EndpointListResponse, - PluginEndpointListResponse, - EndpointDeleteResponse, - EndpointUpdateResponse, - EndpointEnableResponse, - EndpointDisableResponse, ) -def _create_endpoint(tenant_id: str, user_id: str) -> dict[str, bool]: +def _create_endpoint(tenant_id: str, user_id: str) -> bool: """Create a plugin endpoint for the injected workspace and user.""" args = EndpointCreatePayload.model_validate(console_ns.payload) try: - return { - "success": EndpointService.create_endpoint( - tenant_id=tenant_id, - user_id=user_id, - plugin_unique_identifier=args.plugin_unique_identifier, - name=args.name, - settings=args.settings, - ) - } + return EndpointService.create_endpoint( + tenant_id=tenant_id, + user_id=user_id, + plugin_unique_identifier=args.plugin_unique_identifier, + name=args.name, + settings=args.settings, + ) except PluginPermissionDeniedError as e: raise ValueError(e.description) from e -def _update_endpoint(tenant_id: str, user_id: str, endpoint_id: str) -> dict[str, bool]: +def _update_endpoint(tenant_id: str, user_id: str, endpoint_id: str) -> bool: """Update a plugin endpoint identified by the canonical path parameter.""" args = EndpointUpdatePayload.model_validate(console_ns.payload) - return { - "success": EndpointService.update_endpoint( - tenant_id=tenant_id, - user_id=user_id, - endpoint_id=endpoint_id, - name=args.name, - settings=args.settings, - ) - } + return EndpointService.update_endpoint( + tenant_id=tenant_id, + user_id=user_id, + endpoint_id=endpoint_id, + name=args.name, + settings=args.settings, + ) -def _delete_endpoint(tenant_id: str, user_id: str, endpoint_id: str) -> dict[str, bool]: +def _legacy_update_endpoint(tenant_id: str, user_id: str) -> bool: + args = LegacyEndpointUpdatePayload.model_validate(console_ns.payload) + return EndpointService.update_endpoint( + tenant_id=tenant_id, + user_id=user_id, + endpoint_id=args.endpoint_id, + name=args.name, + settings=args.settings, + ) + + +def _delete_endpoint(tenant_id: str, user_id: str, endpoint_id: str) -> bool: """Delete a plugin endpoint identified by the canonical path parameter.""" - return { - "success": EndpointService.delete_endpoint( - tenant_id=tenant_id, - user_id=user_id, - endpoint_id=endpoint_id, - ) - } + return EndpointService.delete_endpoint( + tenant_id=tenant_id, + user_id=user_id, + endpoint_id=endpoint_id, + ) + + +def _delete_endpoint_from_payload(tenant_id: str, user_id: str) -> bool: + args = EndpointIdPayload.model_validate(console_ns.payload) + return _delete_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=args.endpoint_id) + + +def _set_endpoint_enabled(tenant_id: str, user_id: str, *, enabled: bool) -> bool: + args = EndpointIdPayload.model_validate(console_ns.payload) + action = EndpointService.enable_endpoint if enabled else EndpointService.disable_endpoint + return action(tenant_id=tenant_id, user_id=user_id, endpoint_id=args.endpoint_id) @console_ns.route("/workspaces/current/endpoints") @@ -161,11 +234,11 @@ class EndpointCollectionApi(Resource): @console_ns.doc(description="Create a new plugin endpoint") @console_ns.expect(console_ns.models[EndpointCreatePayload.__name__]) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint created successfully", - console_ns.models[EndpointCreateResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -174,7 +247,7 @@ class EndpointCollectionApi(Resource): @with_current_user_id @with_current_tenant_id def post(self, tenant_id: str, user_id: str): - return _create_endpoint(tenant_id=tenant_id, user_id=user_id) + return SuccessResponse(success=_create_endpoint(tenant_id=tenant_id, user_id=user_id)).model_dump(mode="json") @console_ns.route("/workspaces/current/endpoints/create") @@ -190,11 +263,11 @@ class DeprecatedEndpointCreateApi(Resource): ) @console_ns.expect(console_ns.models[EndpointCreatePayload.__name__]) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint created successfully", - console_ns.models[EndpointCreateResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -203,7 +276,7 @@ class DeprecatedEndpointCreateApi(Resource): @with_current_user_id @with_current_tenant_id def post(self, tenant_id: str, user_id: str): - return _create_endpoint(tenant_id=tenant_id, user_id=user_id) + return SuccessResponse(success=_create_endpoint(tenant_id=tenant_id, user_id=user_id)).model_dump(mode="json") @console_ns.route("/workspaces/current/endpoints/list") @@ -212,7 +285,7 @@ class EndpointListApi(Resource): @console_ns.doc(description="List plugin endpoints with pagination") @console_ns.doc(params=query_params_from_model(EndpointListQuery)) @console_ns.response( - 200, + HTTPStatus.OK, "Success", console_ns.models[EndpointListResponse.__name__], ) @@ -224,20 +297,15 @@ class EndpointListApi(Resource): def get(self, tenant_id: str, user_id: str): args = EndpointListQuery.model_validate(request.args.to_dict(flat=True)) - page = args.page - page_size = args.page_size - - return jsonable_encoder( - { - "endpoints": EndpointService.list_endpoints( - tenant_id=tenant_id, - user_id=user_id, - page=page, - page_size=page_size, - ) - } + endpoints = EndpointService.list_endpoints( + tenant_id=tenant_id, + user_id=user_id, + page=args.page, + page_size=args.page_size, ) + return EndpointListResponse(endpoints=endpoints).model_dump(mode="json") + @console_ns.route("/workspaces/current/endpoints/list/plugin") class EndpointListForSinglePluginApi(Resource): @@ -245,9 +313,9 @@ class EndpointListForSinglePluginApi(Resource): @console_ns.doc(description="List endpoints for a specific plugin") @console_ns.doc(params=query_params_from_model(EndpointListForPluginQuery)) @console_ns.response( - 200, + HTTPStatus.OK, "Success", - console_ns.models[PluginEndpointListResponse.__name__], + console_ns.models[EndpointListResponse.__name__], ) @setup_required @login_required @@ -257,22 +325,16 @@ class EndpointListForSinglePluginApi(Resource): def get(self, tenant_id: str, user_id: str): args = EndpointListForPluginQuery.model_validate(request.args.to_dict(flat=True)) - page = args.page - page_size = args.page_size - plugin_id = args.plugin_id - - return jsonable_encoder( - { - "endpoints": EndpointService.list_endpoints_for_single_plugin( - tenant_id=tenant_id, - user_id=user_id, - plugin_id=plugin_id, - page=page, - page_size=page_size, - ) - } + endpoints = EndpointService.list_endpoints_for_single_plugin( + tenant_id=tenant_id, + user_id=user_id, + plugin_id=args.plugin_id, + page=args.page, + page_size=args.page_size, ) + return EndpointListResponse(endpoints=endpoints).model_dump(mode="json") + @console_ns.route("/workspaces/current/endpoints/") class EndpointItemApi(Resource): @@ -282,11 +344,11 @@ class EndpointItemApi(Resource): @console_ns.doc(description="Delete a plugin endpoint") @console_ns.doc(params={"id": {"description": "Endpoint ID", "type": "string", "required": True}}) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint deleted successfully", - console_ns.models[EndpointDeleteResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -295,18 +357,20 @@ class EndpointItemApi(Resource): @with_current_user_id @with_current_tenant_id def delete(self, tenant_id: str, user_id: str, id: str): - return _delete_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=id) + return SuccessResponse( + success=_delete_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=id) + ).model_dump(mode="json") @console_ns.doc("update_endpoint") @console_ns.doc(description="Update a plugin endpoint") @console_ns.expect(console_ns.models[EndpointUpdatePayload.__name__]) @console_ns.doc(params={"id": {"description": "Endpoint ID", "type": "string", "required": True}}) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint updated successfully", - console_ns.models[EndpointUpdateResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -315,7 +379,9 @@ class EndpointItemApi(Resource): @with_current_user_id @with_current_tenant_id def patch(self, tenant_id: str, user_id: str, id: str): - return _update_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=id) + return SuccessResponse( + success=_update_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=id) + ).model_dump(mode="json") @console_ns.route("/workspaces/current/endpoints/delete") @@ -332,11 +398,11 @@ class DeprecatedEndpointDeleteApi(Resource): ) @console_ns.expect(console_ns.models[EndpointIdPayload.__name__]) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint deleted successfully", - console_ns.models[EndpointDeleteResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -345,8 +411,9 @@ class DeprecatedEndpointDeleteApi(Resource): @with_current_user_id @with_current_tenant_id def post(self, tenant_id: str, user_id: str): - args = EndpointIdPayload.model_validate(console_ns.payload) - return _delete_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=args.endpoint_id) + return SuccessResponse(success=_delete_endpoint_from_payload(tenant_id=tenant_id, user_id=user_id)).model_dump( + mode="json" + ) @console_ns.route("/workspaces/current/endpoints/update") @@ -363,11 +430,11 @@ class DeprecatedEndpointUpdateApi(Resource): ) @console_ns.expect(console_ns.models[LegacyEndpointUpdatePayload.__name__]) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint updated successfully", - console_ns.models[EndpointUpdateResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -376,8 +443,9 @@ class DeprecatedEndpointUpdateApi(Resource): @with_current_user_id @with_current_tenant_id def post(self, tenant_id: str, user_id: str): - args = LegacyEndpointUpdatePayload.model_validate(console_ns.payload) - return _update_endpoint(tenant_id=tenant_id, user_id=user_id, endpoint_id=args.endpoint_id) + return SuccessResponse(success=_legacy_update_endpoint(tenant_id=tenant_id, user_id=user_id)).model_dump( + mode="json" + ) @console_ns.route("/workspaces/current/endpoints/enable") @@ -386,11 +454,11 @@ class EndpointEnableApi(Resource): @console_ns.doc(description="Enable a plugin endpoint") @console_ns.expect(console_ns.models[EndpointIdPayload.__name__]) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint enabled successfully", - console_ns.models[EndpointEnableResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -399,13 +467,9 @@ class EndpointEnableApi(Resource): @with_current_user_id @with_current_tenant_id def post(self, tenant_id: str, user_id: str): - args = EndpointIdPayload.model_validate(console_ns.payload) - - return { - "success": EndpointService.enable_endpoint( - tenant_id=tenant_id, user_id=user_id, endpoint_id=args.endpoint_id - ) - } + return SuccessResponse( + success=_set_endpoint_enabled(tenant_id=tenant_id, user_id=user_id, enabled=True) + ).model_dump(mode="json") @console_ns.route("/workspaces/current/endpoints/disable") @@ -414,11 +478,11 @@ class EndpointDisableApi(Resource): @console_ns.doc(description="Disable a plugin endpoint") @console_ns.expect(console_ns.models[EndpointIdPayload.__name__]) @console_ns.response( - 200, + HTTPStatus.OK, "Endpoint disabled successfully", - console_ns.models[EndpointDisableResponse.__name__], + console_ns.models[SuccessResponse.__name__], ) - @console_ns.response(403, "Admin privileges required") + @console_ns.response(HTTPStatus.FORBIDDEN, "Admin privileges required") @setup_required @login_required @is_admin_or_owner_required @@ -427,10 +491,6 @@ class EndpointDisableApi(Resource): @with_current_user_id @with_current_tenant_id def post(self, tenant_id: str, user_id: str): - args = EndpointIdPayload.model_validate(console_ns.payload) - - return { - "success": EndpointService.disable_endpoint( - tenant_id=tenant_id, user_id=user_id, endpoint_id=args.endpoint_id - ) - } + return SuccessResponse( + success=_set_endpoint_enabled(tenant_id=tenant_id, user_id=user_id, enabled=False) + ).model_dump(mode="json") diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index 51a6c9b0f3f..7e44d511bcf 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -1,10 +1,12 @@ +from http import HTTPStatus from urllib import parse from uuid import UUID from flask import abort, request from flask_restx import Resource -from pydantic import BaseModel, Field, TypeAdapter +from pydantic import BaseModel, Field, field_validator from sqlalchemy import func, select +from werkzeug.exceptions import NotFound import services from configs import dify_config @@ -30,8 +32,8 @@ from controllers.console.wraps import ( from extensions.ext_database import db from extensions.ext_redis import redis_client from fields.base import ResponseModel -from fields.member_fields import AccountWithRole, AccountWithRoleList -from libs.helper import extract_remote_ip +from fields.member_fields import AccountWithRoleListResponse, AccountWithRoleResponse +from libs.helper import dump_response, extract_remote_ip from libs.login import current_account_with_tenant, login_required from models.account import Account, TenantAccountJoin, TenantAccountRole from services.account_service import AccountService, RegisterService, TenantService @@ -45,6 +47,11 @@ class MemberInvitePayload(BaseModel): role: str language: str | None = None + @field_validator("emails") + @classmethod + def normalize_emails(cls, emails: list[str]) -> list[str]: + return list(dict.fromkeys(email.lower() for email in emails)) + class MemberRoleUpdatePayload(BaseModel): role: str @@ -70,14 +77,14 @@ class MemberInviteResultResponse(ResponseModel): message: str | None = None -class MemberInviteResponse(ResponseModel): +class MemberActionResponse(ResponseModel): result: str - invitation_results: list[MemberInviteResultResponse] tenant_id: str -class MemberActionTenantResponse(ResponseModel): +class MemberInviteResponse(ResponseModel): result: str + invitation_results: list[MemberInviteResultResponse] tenant_id: str @@ -92,13 +99,14 @@ register_schema_models( ) register_response_schema_models( console_ns, - AccountWithRole, - AccountWithRoleList, + AccountWithRoleResponse, + AccountWithRoleListResponse, + MemberActionResponse, + MemberInviteResponse, + MemberInviteResultResponse, SimpleResultDataResponse, SimpleResultResponse, VerificationTokenResponse, - MemberInviteResponse, - MemberActionTenantResponse, ) @@ -124,10 +132,6 @@ def _normalize_enum_value(value: object) -> str: return str(normalized) if normalized is not None else "" -def _normalize_invitee_emails(emails: list[str]) -> list[str]: - return list(dict.fromkeys(email.lower() for email in emails)) - - def _count_new_member_invites(tenant_id: str, emails: list[str]) -> int: new_member_count = 0 for email in emails: @@ -179,7 +183,7 @@ class MemberListApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountWithRoleList.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountWithRoleListResponse.__name__]) @with_current_user def get(self, current_user: Account | None = None): if current_user is None: @@ -216,9 +220,7 @@ class MemberListApi(Resource): } ) - member_models = TypeAdapter(list[AccountWithRole]).validate_python(serialized_members) - response = AccountWithRoleList(accounts=member_models) - return response.model_dump(mode="json"), 200 + return dump_response(AccountWithRoleListResponse, {"accounts": serialized_members}), HTTPStatus.OK @console_ns.route("/workspaces/current/members/invite-email") @@ -226,7 +228,7 @@ class MemberInviteEmailApi(Resource): """Invite a new member by email.""" @console_ns.expect(console_ns.models[MemberInvitePayload.__name__]) - @console_ns.response(201, "Success", console_ns.models[MemberInviteResponse.__name__]) + @console_ns.response(HTTPStatus.CREATED, "Success", console_ns.models[MemberInviteResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -235,26 +237,26 @@ class MemberInviteEmailApi(Resource): payload = console_ns.payload or {} args = MemberInvitePayload.model_validate(payload) - invitee_emails = _normalize_invitee_emails(args.emails) + invitee_emails = args.emails invitee_role = args.role interface_language = args.language if not dify_config.RBAC_ENABLED: if not TenantAccountRole.is_valid_role(invitee_role): - return {"code": "invalid-role", "message": "Invalid role"}, 400 + return {"code": "invalid-role", "message": "Invalid role"}, HTTPStatus.BAD_REQUEST if not TenantAccountRole.is_non_owner_role(TenantAccountRole(invitee_role)): - return {"code": "invalid-role", "message": "Invalid role"}, 400 + return {"code": "invalid-role", "message": "Invalid role"}, HTTPStatus.BAD_REQUEST inviter = current_user if not inviter.current_tenant: raise ValueError("No current tenant") if not _is_role_enabled(invitee_role, inviter.current_tenant.id): - return {"code": "invalid-role", "message": "Invalid role"}, 400 + return {"code": "invalid-role", "message": "Invalid role"}, HTTPStatus.BAD_REQUEST # Check workspace permission for member invitations from libs.workspace_permission import check_workspace_member_invite_permission check_workspace_member_invite_permission(inviter.current_tenant.id) - invitation_results = [] + invitation_results: list[MemberInviteResultResponse] = [] console_web_url = dify_config.CONSOLE_WEB_URL tenant_id = inviter.current_tenant.id @@ -277,63 +279,65 @@ class MemberInviteEmailApi(Resource): ) encoded_invitee_email = parse.quote(invitee_email) invitation_results.append( - { - "status": "success", - "email": invitee_email, - "url": f"{console_web_url}/activate?email={encoded_invitee_email}&token={token}", - } + MemberInviteResultResponse( + status="success", + email=invitee_email, + url=f"{console_web_url}/activate?email={encoded_invitee_email}&token={token}", + ) ) except AccountAlreadyInTenantError: invitation_results.append( - { - "status": "already_member", - "email": invitee_email, - "message": "Account already in workspace.", - } + MemberInviteResultResponse( + status="already_member", + email=invitee_email, + message="Account already in workspace.", + ) ) except Exception as e: - invitation_results.append({"status": "failed", "email": invitee_email, "message": str(e)}) + invitation_results.append( + MemberInviteResultResponse(status="failed", email=invitee_email, message=str(e)) + ) - return { - "result": "success", - "invitation_results": invitation_results, - "tenant_id": str(inviter.current_tenant.id) if inviter.current_tenant else "", - }, 201 + return MemberInviteResponse( + result="success", + invitation_results=invitation_results, + tenant_id=inviter.current_tenant.id if inviter.current_tenant else "", + ).model_dump(mode="json"), HTTPStatus.CREATED @console_ns.route("/workspaces/current/members/") class MemberCancelInviteApi(Resource): """Cancel an invitation by member id.""" - @console_ns.response(200, "Success", console_ns.models[MemberActionTenantResponse.__name__]) @setup_required @login_required @account_initialization_required + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[MemberActionResponse.__name__]) @with_current_user def delete(self, current_user: Account, member_id: UUID): if not current_user.current_tenant: raise ValueError("No current tenant") member = db.session.get(Account, str(member_id)) if member is None: - abort(404) + abort(HTTPStatus.NOT_FOUND) else: try: TenantService.remove_member_from_tenant( current_user.current_tenant, member, current_user, session=db.session ) except services.errors.account.CannotOperateSelfError as e: - return {"code": "cannot-operate-self", "message": str(e)}, 400 + return {"code": "cannot-operate-self", "message": str(e)}, HTTPStatus.BAD_REQUEST except services.errors.account.NoPermissionError as e: - return {"code": "forbidden", "message": str(e)}, 403 + return {"code": "forbidden", "message": str(e)}, HTTPStatus.FORBIDDEN except services.errors.account.MemberNotInTenantError as e: - return {"code": "member-not-found", "message": str(e)}, 404 + return {"code": "member-not-found", "message": str(e)}, HTTPStatus.NOT_FOUND except Exception as e: raise ValueError(str(e)) - return { - "result": "success", - "tenant_id": str(current_user.current_tenant.id) if current_user.current_tenant else "", - }, 200 + return MemberActionResponse( + result="success", + tenant_id=current_user.current_tenant.id if current_user.current_tenant else "", + ).model_dump(mode="json"), HTTPStatus.OK @console_ns.route("/workspaces/current/members//update-role") @@ -341,7 +345,7 @@ class MemberUpdateRoleApi(Resource): """Update member role.""" @console_ns.expect(console_ns.models[MemberRoleUpdatePayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -352,14 +356,14 @@ class MemberUpdateRoleApi(Resource): new_role = args.role if not TenantAccountRole.is_valid_role(new_role): - return {"code": "invalid-role", "message": "Invalid role"}, 400 + return {"code": "invalid-role", "message": "Invalid role"}, HTTPStatus.BAD_REQUEST if not current_user.current_tenant: raise ValueError("No current tenant") if not _is_role_enabled(new_role, current_user.current_tenant.id): - return {"code": "invalid-role", "message": "Invalid role"}, 400 + return {"code": "invalid-role", "message": "Invalid role"}, HTTPStatus.BAD_REQUEST member = db.session.get(Account, str(member_id)) if not member: - abort(404) + abort(HTTPStatus.NOT_FOUND) try: assert member is not None, "Member not found" @@ -367,17 +371,17 @@ class MemberUpdateRoleApi(Resource): current_user.current_tenant, member, new_role, current_user, session=db.session ) except services.errors.account.CannotOperateSelfError as e: - return {"code": "cannot-operate-self", "message": str(e)}, 400 + return {"code": "cannot-operate-self", "message": str(e)}, HTTPStatus.BAD_REQUEST except services.errors.account.NoPermissionError as e: - return {"code": "forbidden", "message": str(e)}, 403 + return {"code": "forbidden", "message": str(e)}, HTTPStatus.FORBIDDEN except services.errors.account.MemberNotInTenantError as e: - return {"code": "member-not-found", "message": str(e)}, 404 + return {"code": "member-not-found", "message": str(e)}, HTTPStatus.NOT_FOUND except services.errors.account.RoleAlreadyAssignedError as e: - return {"code": "role-already-assigned", "message": str(e)}, 400 + return {"code": "role-already-assigned", "message": str(e)}, HTTPStatus.BAD_REQUEST except Exception as e: raise ValueError(str(e)) - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") @console_ns.route("/workspaces/current/dataset-operators") @@ -387,15 +391,13 @@ class DatasetOperatorMemberListApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[AccountWithRoleList.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[AccountWithRoleListResponse.__name__]) @with_current_user def get(self, current_user: Account): if not current_user.current_tenant: raise ValueError("No current tenant") members = TenantService.get_dataset_operator_members(current_user.current_tenant, session=db.session) - member_models = TypeAdapter(list[AccountWithRole]).validate_python(members, from_attributes=True) - response = AccountWithRoleList(accounts=member_models) - return response.model_dump(mode="json"), 200 + return dump_response(AccountWithRoleListResponse, {"accounts": members}), HTTPStatus.OK @console_ns.route("/workspaces/current/members/send-owner-transfer-confirm-email") @@ -403,7 +405,7 @@ class SendOwnerTransferEmailApi(Resource): """Send owner transfer email.""" @console_ns.expect(console_ns.models[OwnerTransferEmailPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultDataResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultDataResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -435,13 +437,13 @@ class SendOwnerTransferEmailApi(Resource): workspace_name=current_user.current_tenant.name if current_user.current_tenant else "", ) - return {"result": "success", "data": token} + return SimpleResultDataResponse(result="success", data=token).model_dump(mode="json") @console_ns.route("/workspaces/current/members/owner-transfer-check") class OwnerTransferCheckApi(Resource): @console_ns.expect(console_ns.models[OwnerTransferCheckPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[VerificationTokenResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[VerificationTokenResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -480,13 +482,13 @@ class OwnerTransferCheckApi(Resource): _, new_token = AccountService.generate_owner_transfer_token(user_email, code=args.code, additional_data={}) AccountService.reset_owner_transfer_error_rate_limit(user_email) - return {"is_valid": True, "email": token_data.get("email"), "token": new_token} + return VerificationTokenResponse(is_valid=True, email=user_email, token=new_token).model_dump(mode="json") @console_ns.route("/workspaces/current/members//owner-transfer") class OwnerTransfer(Resource): @console_ns.expect(console_ns.models[OwnerTransferPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SimpleResultResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -516,8 +518,7 @@ class OwnerTransfer(Resource): member = db.session.get(Account, str(member_id)) if not member: - abort(404) - return # Never reached, but helps type checker + raise NotFound() if not current_user.current_tenant: raise ValueError("No current tenant") @@ -546,4 +547,4 @@ class OwnerTransfer(Resource): except Exception as e: raise ValueError(str(e)) - return {"result": "success"} + return SimpleResultResponse(result="success").model_dump(mode="json") diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 2dede15330c..682aa5b6190 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -42,7 +42,15 @@ from fields.base import ResponseModel from graphon.model_runtime.utils.encoders import jsonable_encoder from libs.helper import dump_response from libs.login import login_required -from models.account import Account, TenantPluginAutoUpgradeStrategy, TenantPluginPermission +from models.account import ( + Account, + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategy, + TenantPluginAutoUpgradeStrategySetting, + TenantPluginDebugPermission, + TenantPluginInstallPermission, +) from models.provider_ids import ToolProviderID from services.entities.model_provider_entities import ProviderEntityResponse from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService @@ -52,9 +60,9 @@ from services.tools.tools_transform_service import ToolTransformService class AutoUpgradeSettingsResponse(TypedDict): - strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting + strategy_setting: TenantPluginAutoUpgradeStrategySetting upgrade_time_of_day: int - upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode + upgrade_mode: TenantPluginAutoUpgradeMode exclude_plugins: list[str] include_plugins: list[str] @@ -127,8 +135,8 @@ class ParserUninstall(BaseModel): class ParserPermissionChange(BaseModel): - install_permission: TenantPluginPermission.InstallPermission = TenantPluginPermission.InstallPermission.EVERYONE - debug_permission: TenantPluginPermission.DebugPermission = TenantPluginPermission.DebugPermission.EVERYONE + install_permission: TenantPluginInstallPermission = TenantPluginInstallPermission.EVERYONE + debug_permission: TenantPluginDebugPermission = TenantPluginDebugPermission.EVERYONE class ParserDynamicOptions(BaseModel): @@ -150,16 +158,14 @@ class ParserDynamicOptionsWithCredentials(BaseModel): class PluginPermissionSettingsPayload(BaseModel): - install_permission: TenantPluginPermission.InstallPermission = TenantPluginPermission.InstallPermission.EVERYONE - debug_permission: TenantPluginPermission.DebugPermission = TenantPluginPermission.DebugPermission.EVERYONE + install_permission: TenantPluginInstallPermission = TenantPluginInstallPermission.EVERYONE + debug_permission: TenantPluginDebugPermission = TenantPluginDebugPermission.EVERYONE class PluginAutoUpgradeSettingsPayload(BaseModel): - strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting = ( - TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY - ) + strategy_setting: TenantPluginAutoUpgradeStrategySetting = TenantPluginAutoUpgradeStrategySetting.FIX_ONLY upgrade_time_of_day: int = 0 - upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode = TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE + upgrade_mode: TenantPluginAutoUpgradeMode = TenantPluginAutoUpgradeMode.EXCLUDE exclude_plugins: list[str] = Field(default_factory=list) include_plugins: list[str] = Field(default_factory=list) @@ -170,15 +176,15 @@ class PluginAutoUpgradeChangeResponse(ResponseModel): class PluginAutoUpgradeSettingsResponseModel(ResponseModel): - strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting + strategy_setting: TenantPluginAutoUpgradeStrategySetting upgrade_time_of_day: int - upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode + upgrade_mode: TenantPluginAutoUpgradeMode exclude_plugins: list[str] include_plugins: list[str] class PluginAutoUpgradeFetchResponse(ResponseModel): - category: TenantPluginAutoUpgradeStrategy.PluginCategory + category: TenantPluginAutoUpgradeCategory auto_upgrade: PluginAutoUpgradeSettingsResponseModel @@ -209,19 +215,19 @@ class PluginDeclarationResponse(ResponseModel): class ParserAutoUpgradeChange(BaseModel): model_config = ConfigDict(extra="forbid") - category: TenantPluginAutoUpgradeStrategy.PluginCategory + category: TenantPluginAutoUpgradeCategory auto_upgrade: PluginAutoUpgradeSettingsPayload class ParserAutoUpgradeFetch(BaseModel): - category: TenantPluginAutoUpgradeStrategy.PluginCategory + category: TenantPluginAutoUpgradeCategory class ParserExcludePlugin(BaseModel): model_config = ConfigDict(extra="forbid") plugin_id: str - category: TenantPluginAutoUpgradeStrategy.PluginCategory + category: TenantPluginAutoUpgradeCategory class ParserReadme(BaseModel): @@ -339,8 +345,8 @@ class PluginTaskResponse(ResponseModel): class PluginPermissionResponse(ResponseModel): - install_permission: TenantPluginPermission.InstallPermission - debug_permission: TenantPluginPermission.DebugPermission + install_permission: TenantPluginInstallPermission + debug_permission: TenantPluginDebugPermission class PluginDynamicOptionsResponse(ResponseModel): @@ -408,22 +414,22 @@ register_response_schema_models( register_enum_models( console_ns, - TenantPluginPermission.DebugPermission, - TenantPluginAutoUpgradeStrategy.PluginCategory, - TenantPluginAutoUpgradeStrategy.UpgradeMode, - TenantPluginAutoUpgradeStrategy.StrategySetting, - TenantPluginPermission.InstallPermission, + TenantPluginDebugPermission, + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategySetting, + TenantPluginInstallPermission, ) def _default_auto_upgrade_settings( tenant_id: str, - category: TenantPluginAutoUpgradeStrategy.PluginCategory, + category: TenantPluginAutoUpgradeCategory, ) -> AutoUpgradeSettingsResponse: return { "strategy_setting": PluginAutoUpgradeService.default_strategy_setting_for_category(category), "upgrade_time_of_day": PluginAutoUpgradeService.default_upgrade_time_of_day(tenant_id), - "upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + "upgrade_mode": TenantPluginAutoUpgradeMode.EXCLUDE, "exclude_plugins": [], "include_plugins": [], } @@ -987,8 +993,8 @@ class PluginFetchPermissionApi(Resource): if not permission: return jsonable_encoder( { - "install_permission": TenantPluginPermission.InstallPermission.EVERYONE, - "debug_permission": TenantPluginPermission.DebugPermission.EVERYONE, + "install_permission": TenantPluginInstallPermission.EVERYONE, + "debug_permission": TenantPluginDebugPermission.EVERYONE, } ) diff --git a/api/controllers/console/workspace/workspace.py b/api/controllers/console/workspace/workspace.py index 418c3eb66e1..0630281de75 100644 --- a/api/controllers/console/workspace/workspace.py +++ b/api/controllers/console/workspace/workspace.py @@ -1,8 +1,9 @@ import logging from datetime import datetime +from http import HTTPStatus from flask import request -from flask_restx import Resource, fields, marshal +from flask_restx import Resource from pydantic import BaseModel, Field, field_validator from sqlalchemy import select from werkzeug.exceptions import Unauthorized @@ -16,7 +17,12 @@ from controllers.common.errors import ( TooManyFilesError, UnsupportedFileTypeError, ) -from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models +from controllers.common.schema import ( + query_params_from_model, + query_params_from_request, + register_response_schema_models, + register_schema_models, +) from controllers.console import console_ns from controllers.console.admin import admin_required from controllers.console.error import AccountNotLinkTenantError @@ -31,7 +37,7 @@ from controllers.console.wraps import ( from enums.cloud_plan import CloudPlan from extensions.ext_database import db from fields.base import ResponseModel -from libs.helper import OptionalTimestampField, TimestampField, dump_response, to_timestamp +from libs.helper import dump_response, to_timestamp from libs.login import login_required from libs.pagination import paginate_query from models.account import Account, Tenant, TenantAccountJoin, TenantCustomConfigDict, TenantStatus @@ -133,7 +139,7 @@ class WorkspaceListItemResponse(ResponseModel): @field_validator("status", mode="before") @classmethod - def _normalize_status(cls, value): + def _normalize_enum_like(cls, value): if value is None: return None if isinstance(value, str): @@ -146,7 +152,7 @@ class WorkspaceListItemResponse(ResponseModel): return to_timestamp(value) -class WorkspaceListResponse(ResponseModel): +class WorkspacePaginationResponse(ResponseModel): data: list[WorkspaceListItemResponse] has_more: bool limit: int @@ -159,7 +165,7 @@ class SwitchWorkspaceResponse(ResponseModel): new_tenant: TenantInfoResponse -class WorkspaceMutationResponse(ResponseModel): +class WorkspaceTenantResultResponse(ResponseModel): result: str tenant: TenantInfoResponse @@ -174,6 +180,16 @@ class WorkspacePermissionResponse(ResponseModel): allow_owner_transfer: bool +WORKSPACE_LOGO_UPLOAD_PARAMS = { + "file": { + "in": "formData", + "type": "file", + "required": True, + "description": "Workspace web app logo file. Only SVG and PNG files are supported.", + } +} + + register_schema_models( console_ns, WorkspaceListQuery, @@ -184,53 +200,21 @@ register_schema_models( register_response_schema_models( console_ns, TenantInfoResponse, + TenantListItemResponse, TenantListResponse, - WorkspaceListResponse, - SwitchWorkspaceResponse, - WorkspaceMutationResponse, - WorkspaceLogoUploadResponse, WorkspaceCustomConfigResponse, + WorkspaceListItemResponse, + WorkspacePaginationResponse, + SwitchWorkspaceResponse, + WorkspaceTenantResultResponse, + WorkspaceLogoUploadResponse, WorkspacePermissionResponse, ) -provider_fields = { - "provider_name": fields.String, - "provider_type": fields.String, - "is_valid": fields.Boolean, - "token_is_set": fields.Boolean, -} - -tenant_fields = { - "id": fields.String, - "name": fields.String, - "plan": fields.String, - "status": fields.String, - "created_at": TimestampField, - "role": fields.String, - "in_trial": fields.Boolean, - "trial_end_reason": fields.String, - "custom_config": fields.Raw(attribute="custom_config"), - "trial_credits": fields.Integer, - "trial_credits_used": fields.Integer, - "next_credit_reset_date": fields.Integer, -} - -tenants_fields = { - "id": fields.String, - "name": fields.String, - "plan": fields.String, - "status": fields.String, - "created_at": TimestampField, - "last_opened_at": OptionalTimestampField, - "current": fields.Boolean, -} - -workspace_fields = {"id": fields.String, "name": fields.String, "status": fields.String, "created_at": TimestampField} - @console_ns.route("/workspaces") class TenantListApi(Resource): - @console_ns.response(200, "Success", console_ns.models[TenantListResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[TenantListResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -281,18 +265,17 @@ class TenantListApi(Resource): tenant_dicts.append(tenant_dict) - return {"workspaces": marshal(tenant_dicts, tenants_fields)}, 200 + return dump_response(TenantListResponse, {"workspaces": tenant_dicts}), HTTPStatus.OK @console_ns.route("/all-workspaces") class WorkspaceListApi(Resource): @console_ns.doc(params=query_params_from_model(WorkspaceListQuery)) - @console_ns.response(200, "Success", console_ns.models[WorkspaceListResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[WorkspacePaginationResponse.__name__]) @setup_required @admin_required def get(self): - payload = request.args.to_dict(flat=True) - args = WorkspaceListQuery.model_validate(payload) + args = query_params_from_request(WorkspaceListQuery) stmt = select(Tenant).order_by(Tenant.created_at.desc()) tenants = paginate_query(stmt, page=args.page, per_page=args.limit) @@ -301,13 +284,9 @@ class WorkspaceListApi(Resource): if tenants.has_next: has_more = True - return { - "data": marshal(tenants.items, workspace_fields), - "has_more": has_more, - "limit": args.limit, - "page": args.page, - "total": tenants.total, - }, 200 + return WorkspacePaginationResponse( + data=tenants.items, has_more=has_more, limit=args.limit, page=args.page, total=tenants.total or 0 + ).model_dump(mode="json"), HTTPStatus.OK @console_ns.route("/workspaces/current", endpoint="workspaces_current") @@ -316,7 +295,7 @@ class TenantApi(Resource): @setup_required @login_required @account_initialization_required - @console_ns.response(200, "Success", console_ns.models[TenantInfoResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[TenantInfoResponse.__name__]) @with_current_user def post(self, current_user: Account): if request.path == "/info": @@ -336,13 +315,13 @@ class TenantApi(Resource): else: raise Unauthorized("workspace is archived") - return dump_response(TenantInfoResponse, WorkspaceService.get_tenant_info(tenant)), 200 + return dump_response(TenantInfoResponse, WorkspaceService.get_tenant_info(tenant)), HTTPStatus.OK @console_ns.route("/workspaces/switch") class SwitchWorkspaceApi(Resource): @console_ns.expect(console_ns.models[SwitchWorkspacePayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[SwitchWorkspaceResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[SwitchWorkspaceResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -351,7 +330,7 @@ class SwitchWorkspaceApi(Resource): payload = console_ns.payload or {} args = SwitchWorkspacePayload.model_validate(payload) - # check if tenant_id is valid, 403 if not + # Check whether the tenant_id belongs to the current account. try: TenantService.switch_tenant(current_user, args.tenant_id, session=db.session) except Exception: @@ -361,13 +340,15 @@ class SwitchWorkspaceApi(Resource): if new_tenant is None: raise ValueError("Tenant not found") - return {"result": "success", "new_tenant": marshal(WorkspaceService.get_tenant_info(new_tenant), tenant_fields)} + return SwitchWorkspaceResponse( + result="success", new_tenant=WorkspaceService.get_tenant_info(new_tenant) + ).model_dump(mode="json") @console_ns.route("/workspaces/custom-config") class CustomConfigWorkspaceApi(Resource): @console_ns.expect(console_ns.models[WorkspaceCustomConfigPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[WorkspaceMutationResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[WorkspaceTenantResultResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -390,12 +371,15 @@ class CustomConfigWorkspaceApi(Resource): tenant.custom_config_dict = custom_config_dict db.session.commit() - return {"result": "success", "tenant": marshal(WorkspaceService.get_tenant_info(tenant), tenant_fields)} + return WorkspaceTenantResultResponse( + result="success", tenant=WorkspaceService.get_tenant_info(tenant) + ).model_dump(mode="json") @console_ns.route("/workspaces/custom-config/webapp-logo/upload") class WebappLogoWorkspaceApi(Resource): - @console_ns.response(201, "Logo uploaded", console_ns.models[WorkspaceLogoUploadResponse.__name__]) + @console_ns.doc(consumes=["multipart/form-data"], params=WORKSPACE_LOGO_UPLOAD_PARAMS) + @console_ns.response(HTTPStatus.CREATED, "Logo uploaded", console_ns.models[WorkspaceLogoUploadResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -431,13 +415,13 @@ class WebappLogoWorkspaceApi(Resource): except services.errors.file.UnsupportedFileTypeError: raise UnsupportedFileTypeError() - return {"id": upload_file.id}, 201 + return WorkspaceLogoUploadResponse(id=upload_file.id).model_dump(mode="json"), HTTPStatus.CREATED @console_ns.route("/workspaces/info") class WorkspaceInfoApi(Resource): @console_ns.expect(console_ns.models[WorkspaceInfoPayload.__name__]) - @console_ns.response(200, "Success", console_ns.models[WorkspaceMutationResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[WorkspaceTenantResultResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -453,14 +437,16 @@ class WorkspaceInfoApi(Resource): tenant.name = args.name db.session.commit() - return {"result": "success", "tenant": marshal(WorkspaceService.get_tenant_info(tenant), tenant_fields)} + return WorkspaceTenantResultResponse( + result="success", tenant=WorkspaceService.get_tenant_info(tenant) + ).model_dump(mode="json") @console_ns.route("/workspaces/current/permission") class WorkspacePermissionApi(Resource): """Get workspace permissions for the current workspace.""" - @console_ns.response(200, "Success", console_ns.models[WorkspacePermissionResponse.__name__]) + @console_ns.response(HTTPStatus.OK, "Success", console_ns.models[WorkspacePermissionResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -477,8 +463,8 @@ class WorkspacePermissionApi(Resource): # Get workspace permissions from enterprise service permission = EnterpriseService.WorkspacePermissionService.get_permission(current_tenant_id) - return { - "workspace_id": permission.workspace_id, - "allow_member_invite": permission.allow_member_invite, - "allow_owner_transfer": permission.allow_owner_transfer, - }, 200 + return WorkspacePermissionResponse( + workspace_id=permission.workspace_id, + allow_member_invite=permission.allow_member_invite, + allow_owner_transfer=permission.allow_owner_transfer, + ).model_dump(mode="json"), HTTPStatus.OK diff --git a/api/core/datasource/__base/datasource_provider.py b/api/core/datasource/__base/datasource_provider.py index 4b47777f0b9..d5633d9f081 100644 --- a/api/core/datasource/__base/datasource_provider.py +++ b/api/core/datasource/__base/datasource_provider.py @@ -3,7 +3,7 @@ from typing import Any from core.datasource.__base.datasource_plugin import DatasourcePlugin from core.datasource.entities.datasource_entities import DatasourceProviderEntityWithPlugin, DatasourceProviderType -from core.entities.provider_entities import ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.plugin.impl.tool import PluginToolManager from core.tools.errors import ToolProviderCredentialValidationError @@ -78,11 +78,11 @@ class DatasourcePluginProviderController(ABC): if not credential_schema.required and credentials[credential_name] is None: continue - if credential_schema.type in {ProviderConfig.Type.SECRET_INPUT, ProviderConfig.Type.TEXT_INPUT}: + if credential_schema.type in {ProviderConfigType.SECRET_INPUT, ProviderConfigType.TEXT_INPUT}: if not isinstance(credentials[credential_name], str): raise ToolProviderCredentialValidationError(f"credential {credential_name} should be string") - elif credential_schema.type == ProviderConfig.Type.SELECT: + elif credential_schema.type == ProviderConfigType.SELECT: if not isinstance(credentials[credential_name], str): raise ToolProviderCredentialValidationError(f"credential {credential_name} should be string") @@ -107,9 +107,9 @@ class DatasourcePluginProviderController(ABC): default_value = credential_schema.default # parse default value into the correct type if credential_schema.type in { - ProviderConfig.Type.SECRET_INPUT, - ProviderConfig.Type.TEXT_INPUT, - ProviderConfig.Type.SELECT, + ProviderConfigType.SECRET_INPUT, + ProviderConfigType.TEXT_INPUT, + ProviderConfigType.SELECT, }: default_value = str(default_value) diff --git a/api/core/entities/mcp_provider.py b/api/core/entities/mcp_provider.py index 03b11c7263f..026022db1fb 100644 --- a/api/core/entities/mcp_provider.py +++ b/api/core/entities/mcp_provider.py @@ -9,7 +9,7 @@ from urllib.parse import urlparse from pydantic import BaseModel from configs import dify_config -from core.entities.provider_entities import BasicProviderConfig +from core.entities.provider_entities import BasicProviderConfig, ProviderConfigType from core.helper import encrypter from core.helper.provider_cache import NoOpProviderCredentialCache from core.mcp.types import OAuthClientInformation, OAuthClientMetadata, OAuthTokens @@ -315,7 +315,7 @@ class MCPProviderEntity(BaseModel): return data # Create dynamic config only for encrypted fields - config = [BasicProviderConfig(type=BasicProviderConfig.Type.SECRET_INPUT, name=key) for key in encrypted_fields] + config = [BasicProviderConfig(type=ProviderConfigType.SECRET_INPUT, name=key) for key in encrypted_fields] encrypter_instance, _ = create_provider_encrypter( tenant_id=self.tenant_id, diff --git a/api/core/entities/provider_entities.py b/api/core/entities/provider_entities.py index 4c9cd858e13..4ac7614425c 100644 --- a/api/core/entities/provider_entities.py +++ b/api/core/entities/provider_entities.py @@ -165,34 +165,35 @@ class ModelSettings(BaseModel): model_config = ConfigDict(protected_namespaces=()) +class ProviderConfigType(StrEnum): + SECRET_INPUT = CommonParameterType.SECRET_INPUT + TEXT_INPUT = CommonParameterType.TEXT_INPUT + SELECT = CommonParameterType.SELECT + BOOLEAN = CommonParameterType.BOOLEAN + APP_SELECTOR = CommonParameterType.APP_SELECTOR + MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR + TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR + + @classmethod + def value_of(cls, value: str) -> ProviderConfigType: + """ + Get value of given mode. + + :param value: mode value + :return: mode + """ + for mode in cls: + if mode.value == value: + return mode + raise ValueError(f"invalid mode value {value}") + + class BasicProviderConfig(BaseModel): """ Base model class for common provider settings like credentials """ - class Type(StrEnum): - SECRET_INPUT = CommonParameterType.SECRET_INPUT - TEXT_INPUT = CommonParameterType.TEXT_INPUT - SELECT = CommonParameterType.SELECT - BOOLEAN = CommonParameterType.BOOLEAN - APP_SELECTOR = CommonParameterType.APP_SELECTOR - MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR - TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR - - @classmethod - def value_of(cls, value: str) -> ProviderConfig.Type: - """ - Get value of given mode. - - :param value: mode value - :return: mode - """ - for mode in cls: - if mode.value == value: - return mode - raise ValueError(f"invalid mode value {value}") - - type: Type = Field(..., description="The type of the credentials") + type: ProviderConfigType = Field(..., description="The type of the credentials") name: str = Field(..., description="The name of the credentials") diff --git a/api/core/helper/provider_encryption.py b/api/core/helper/provider_encryption.py index 8484a28c05f..d392d22687b 100644 --- a/api/core/helper/provider_encryption.py +++ b/api/core/helper/provider_encryption.py @@ -3,7 +3,7 @@ from collections.abc import Mapping from copy import deepcopy from typing import Any, Protocol -from core.entities.provider_entities import BasicProviderConfig +from core.entities.provider_entities import BasicProviderConfig, ProviderConfigType from core.helper import encrypter @@ -60,7 +60,7 @@ class ProviderConfigEncrypter: fields[credential.name] = credential for field_name, field in fields.items(): - if field.type == BasicProviderConfig.Type.SECRET_INPUT: + if field.type == ProviderConfigType.SECRET_INPUT: if field_name in data: encrypted = encrypter.encrypt_token(self.tenant_id, data[field_name] or "") data[field_name] = encrypted @@ -81,7 +81,7 @@ class ProviderConfigEncrypter: fields[credential.name] = credential for field_name, field in fields.items(): - if field.type == BasicProviderConfig.Type.SECRET_INPUT: + if field.type == ProviderConfigType.SECRET_INPUT: if field_name in data: if len(data[field_name]) > 6: data[field_name] = ( @@ -112,7 +112,7 @@ class ProviderConfigEncrypter: fields[credential.name] = credential for field_name, field in fields.items(): - if field.type == BasicProviderConfig.Type.SECRET_INPUT: + if field.type == ProviderConfigType.SECRET_INPUT: if field_name in data: with contextlib.suppress(Exception): # if the value is None or empty string, skip decrypt diff --git a/api/core/plugin/entities/bundle.py b/api/core/plugin/entities/bundle.py index 83d72d5dfa9..e3b17dc4710 100644 --- a/api/core/plugin/entities/bundle.py +++ b/api/core/plugin/entities/bundle.py @@ -5,12 +5,13 @@ from pydantic import BaseModel from core.plugin.entities.plugin import PluginDeclaration, PluginInstallationSource -class PluginBundleDependency(BaseModel): - class Type(StrEnum): - Github = PluginInstallationSource.Github.value - Marketplace = PluginInstallationSource.Marketplace.value - Package = PluginInstallationSource.Package.value +class PluginBundleDependencyType(StrEnum): + Github = PluginInstallationSource.Github.value + Marketplace = PluginInstallationSource.Marketplace.value + Package = PluginInstallationSource.Package.value + +class PluginBundleDependency(BaseModel): class Github(BaseModel): repo_address: str repo: str @@ -26,5 +27,5 @@ class PluginBundleDependency(BaseModel): unique_identifier: str manifest: PluginDeclaration - type: Type + type: PluginBundleDependencyType value: Github | Marketplace | Package diff --git a/api/core/plugin/entities/parameters.py b/api/core/plugin/entities/parameters.py index 14ed8af3ef7..4c0abe4ed4a 100644 --- a/api/core/plugin/entities/parameters.py +++ b/api/core/plugin/entities/parameters.py @@ -57,11 +57,12 @@ class MCPServerParameterType(StrEnum): OBJECT = auto() -class PluginParameterAutoGenerate(BaseModel): - class Type(StrEnum): - PROMPT_INSTRUCTION = auto() +class PluginParameterAutoGenerateType(StrEnum): + PROMPT_INSTRUCTION = auto() - type: Type + +class PluginParameterAutoGenerate(BaseModel): + type: PluginParameterAutoGenerateType class PluginParameterTemplate(BaseModel): diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index 89e0e8881cb..9f97de01056 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -166,12 +166,13 @@ class PluginEntity(PluginInstallation): return self -class PluginDependency(BaseModel): - class Type(StrEnum): - Github = PluginInstallationSource.Github - Marketplace = PluginInstallationSource.Marketplace - Package = PluginInstallationSource.Package +class PluginDependencyType(StrEnum): + Github = PluginInstallationSource.Github + Marketplace = PluginInstallationSource.Marketplace + Package = PluginInstallationSource.Package + +class PluginDependency(BaseModel): class Github(BaseModel): repo: str version: str @@ -194,7 +195,7 @@ class PluginDependency(BaseModel): plugin_unique_identifier: str version: str | None = None - type: Type + type: PluginDependencyType value: Github | Marketplace | Package current_identifier: str | None = None diff --git a/api/core/tools/__base/tool_provider.py b/api/core/tools/__base/tool_provider.py index 70e4fe1ff73..39d61409862 100644 --- a/api/core/tools/__base/tool_provider.py +++ b/api/core/tools/__base/tool_provider.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from copy import deepcopy from typing import Any -from core.entities.provider_entities import ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.tools.__base.tool import Tool from core.tools.entities.tool_entities import ( ToolProviderEntity, @@ -71,11 +71,11 @@ class ToolProviderController[ToolProviderEntityT: ToolProviderEntity, ToolProvid if not credential_schema.required and credentials[credential_name] is None: continue - if credential_schema.type in {ProviderConfig.Type.SECRET_INPUT, ProviderConfig.Type.TEXT_INPUT}: + if credential_schema.type in {ProviderConfigType.SECRET_INPUT, ProviderConfigType.TEXT_INPUT}: if not isinstance(credentials[credential_name], str): raise ToolProviderCredentialValidationError(f"credential {credential_name} should be string") - elif credential_schema.type == ProviderConfig.Type.SELECT: + elif credential_schema.type == ProviderConfigType.SELECT: if not isinstance(credentials[credential_name], str): raise ToolProviderCredentialValidationError(f"credential {credential_name} should be string") @@ -100,9 +100,9 @@ class ToolProviderController[ToolProviderEntityT: ToolProviderEntity, ToolProvid default_value = credential_schema.default # parse default value into the correct type if credential_schema.type in { - ProviderConfig.Type.SECRET_INPUT, - ProviderConfig.Type.TEXT_INPUT, - ProviderConfig.Type.SELECT, + ProviderConfigType.SECRET_INPUT, + ProviderConfigType.TEXT_INPUT, + ProviderConfigType.SELECT, }: default_value = str(default_value) diff --git a/api/core/tools/custom_tool/provider.py b/api/core/tools/custom_tool/provider.py index ade5b894f95..df48f63809d 100644 --- a/api/core/tools/custom_tool/provider.py +++ b/api/core/tools/custom_tool/provider.py @@ -5,7 +5,7 @@ from typing import override from pydantic import Field from sqlalchemy import select -from core.entities.provider_entities import ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.tools.__base.tool_provider import ToolProviderController from core.tools.__base.tool_runtime import ToolRuntime from core.tools.custom_tool.tool import ApiTool @@ -41,7 +41,7 @@ class ApiToolProviderController(ToolProviderController[ToolProviderEntity, ApiTo ProviderConfig( name="auth_type", required=True, - type=ProviderConfig.Type.SELECT, + type=ProviderConfigType.SELECT, options=[ ProviderConfig.Option(value="none", label=I18nObject(en_US="None", zh_Hans="无")), ProviderConfig.Option(value="api_key_header", label=I18nObject(en_US="Header", zh_Hans="请求头")), @@ -60,20 +60,20 @@ class ApiToolProviderController(ToolProviderController[ToolProviderEntity, ApiTo name="api_key_header", required=False, default="Authorization", - type=ProviderConfig.Type.TEXT_INPUT, + type=ProviderConfigType.TEXT_INPUT, help=I18nObject(en_US="The header name of the api key", zh_Hans="携带 api key 的 header 名称"), ), ProviderConfig( name="api_key_value", required=True, - type=ProviderConfig.Type.SECRET_INPUT, + type=ProviderConfigType.SECRET_INPUT, help=I18nObject(en_US="The api key", zh_Hans="api key 的值"), ), ProviderConfig( name="api_key_header_prefix", required=False, default="basic", - type=ProviderConfig.Type.SELECT, + type=ProviderConfigType.SELECT, help=I18nObject(en_US="The prefix of the api key header", zh_Hans="api key header 的前缀"), options=[ ProviderConfig.Option(value="basic", label=I18nObject(en_US="Basic", zh_Hans="Basic")), @@ -89,7 +89,7 @@ class ApiToolProviderController(ToolProviderController[ToolProviderEntity, ApiTo name="api_key_query_param", required=False, default="key", - type=ProviderConfig.Type.TEXT_INPUT, + type=ProviderConfigType.TEXT_INPUT, help=I18nObject( en_US="The query parameter name of the api key", zh_Hans="携带 api key 的查询参数名称" ), @@ -97,7 +97,7 @@ class ApiToolProviderController(ToolProviderController[ToolProviderEntity, ApiTo ProviderConfig( name="api_key_value", required=True, - type=ProviderConfig.Type.SECRET_INPUT, + type=ProviderConfigType.SECRET_INPUT, help=I18nObject(en_US="The api key", zh_Hans="api key 的值"), ), ] diff --git a/api/core/trigger/utils/encryption.py b/api/core/trigger/utils/encryption.py index 9b958690e59..8409be0813e 100644 --- a/api/core/trigger/utils/encryption.py +++ b/api/core/trigger/utils/encryption.py @@ -1,7 +1,7 @@ from collections.abc import Mapping from typing import Union, override -from core.entities.provider_entities import BasicProviderConfig, ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.helper.provider_cache import ProviderCredentialsCache from core.helper.provider_encryption import ProviderConfigCache, ProviderConfigEncrypter, create_provider_encrypter from core.plugin.entities.plugin_daemon import CredentialType @@ -142,7 +142,7 @@ def masked_credentials( if not config: masked_credentials[key] = value continue - if config.type == BasicProviderConfig.Type.SECRET_INPUT: + if config.type == ProviderConfigType.SECRET_INPUT: if len(value) <= 4: masked_credentials[key] = "*" * len(value) else: diff --git a/api/models/account.py b/api/models/account.py index df152e1783c..350b811bd46 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -369,17 +369,19 @@ class InvitationCode(TypeBase): ) +class TenantPluginInstallPermission(enum.StrEnum): + EVERYONE = "everyone" + ADMINS = "admins" + NOBODY = "noone" + + +class TenantPluginDebugPermission(enum.StrEnum): + EVERYONE = "everyone" + ADMINS = "admins" + NOBODY = "noone" + + class TenantPluginPermission(TypeBase): - class InstallPermission(enum.StrEnum): - EVERYONE = "everyone" - ADMINS = "admins" - NOBODY = "noone" - - class DebugPermission(enum.StrEnum): - EVERYONE = "everyone" - ADMINS = "admins" - NOBODY = "noone" - __tablename__ = "account_plugin_permissions" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="account_plugin_permission_pkey"), @@ -390,36 +392,42 @@ class TenantPluginPermission(TypeBase): StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) - install_permission: Mapped[InstallPermission] = mapped_column( - EnumText(InstallPermission, length=16), + install_permission: Mapped[TenantPluginInstallPermission] = mapped_column( + EnumText(TenantPluginInstallPermission, length=16), nullable=False, server_default="everyone", - default=InstallPermission.EVERYONE, + default=TenantPluginInstallPermission.EVERYONE, ) - debug_permission: Mapped[DebugPermission] = mapped_column( - EnumText(DebugPermission, length=16), nullable=False, server_default="noone", default=DebugPermission.NOBODY + debug_permission: Mapped[TenantPluginDebugPermission] = mapped_column( + EnumText(TenantPluginDebugPermission, length=16), + nullable=False, + server_default="noone", + default=TenantPluginDebugPermission.NOBODY, ) +class TenantPluginAutoUpgradeCategory(enum.StrEnum): + TOOL = "tool" + MODEL = "model" + EXTENSION = "extension" + AGENT_STRATEGY = "agent-strategy" + DATASOURCE = "datasource" + TRIGGER = "trigger" + + +class TenantPluginAutoUpgradeStrategySetting(enum.StrEnum): + DISABLED = "disabled" + FIX_ONLY = "fix_only" + LATEST = "latest" + + +class TenantPluginAutoUpgradeMode(enum.StrEnum): + ALL = "all" + PARTIAL = "partial" + EXCLUDE = "exclude" + + class TenantPluginAutoUpgradeStrategy(TypeBase): - class PluginCategory(enum.StrEnum): - TOOL = "tool" - MODEL = "model" - EXTENSION = "extension" - AGENT_STRATEGY = "agent-strategy" - DATASOURCE = "datasource" - TRIGGER = "trigger" - - class StrategySetting(enum.StrEnum): - DISABLED = "disabled" - FIX_ONLY = "fix_only" - LATEST = "latest" - - class UpgradeMode(enum.StrEnum): - ALL = "all" - PARTIAL = "partial" - EXCLUDE = "exclude" - __tablename__ = "tenant_plugin_auto_upgrade_strategies" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="tenant_plugin_auto_upgrade_strategy_pkey"), @@ -431,20 +439,23 @@ class TenantPluginAutoUpgradeStrategy(TypeBase): StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) - category: Mapped[PluginCategory] = mapped_column( - EnumText(PluginCategory, length=32), + category: Mapped[TenantPluginAutoUpgradeCategory] = mapped_column( + EnumText(TenantPluginAutoUpgradeCategory, length=32), nullable=False, server_default="tool", - default=PluginCategory.TOOL, + default=TenantPluginAutoUpgradeCategory.TOOL, ) - strategy_setting: Mapped[StrategySetting] = mapped_column( - EnumText(StrategySetting, length=16), + strategy_setting: Mapped[TenantPluginAutoUpgradeStrategySetting] = mapped_column( + EnumText(TenantPluginAutoUpgradeStrategySetting, length=16), nullable=False, server_default="fix_only", - default=StrategySetting.FIX_ONLY, + default=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, ) - upgrade_mode: Mapped[UpgradeMode] = mapped_column( - EnumText(UpgradeMode, length=16), nullable=False, server_default="exclude", default=UpgradeMode.EXCLUDE + upgrade_mode: Mapped[TenantPluginAutoUpgradeMode] = mapped_column( + EnumText(TenantPluginAutoUpgradeMode, length=16), + nullable=False, + server_default="exclude", + default=TenantPluginAutoUpgradeMode.EXCLUDE, ) exclude_plugins: Mapped[list[str]] = mapped_column(sa.JSON, nullable=False, default_factory=list) include_plugins: Mapped[list[str]] = mapped_column(sa.JSON, nullable=False, default_factory=list) diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 5f4ecad431c..d9e63e05012 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -141,9 +141,9 @@ Get account avatar url #### Responses -| Code | Description | Schema | -| ---- | ----------- | ------ | -| 200 | Success | **application/json**: [EducationActivateResponse](#educationactivateresponse)
| +| Code | Description | +| ---- | ----------- | +| 200 | Success | ### [GET] /account/education/autocomplete #### Parameters @@ -1411,7 +1411,7 @@ Infer CLI tool + ENV suggestions from a standardized Agent App skill | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [WorkspaceListResponse](#workspacelistresponse)
| +| 200 | Success | **application/json**: [WorkspacePaginationResponse](#workspacepaginationresponse)
| ### [GET] /api-based-extension Get all API-based extensions for current tenant @@ -9983,7 +9983,7 @@ Create a new plugin endpoint | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint created successfully | **application/json**: [EndpointCreateResponse](#endpointcreateresponse)
| +| 200 | Endpoint created successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### ~~[POST] /workspaces/current/endpoints/create~~ @@ -10002,7 +10002,7 @@ Deprecated legacy alias for creating a plugin endpoint. Use POST /workspaces/cur | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint created successfully | **application/json**: [EndpointCreateResponse](#endpointcreateresponse)
| +| 200 | Endpoint created successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### ~~[POST] /workspaces/current/endpoints/delete~~ @@ -10021,7 +10021,7 @@ Deprecated legacy alias for deleting a plugin endpoint. Use DELETE /workspaces/c | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint deleted successfully | **application/json**: [EndpointDeleteResponse](#endpointdeleteresponse)
| +| 200 | Endpoint deleted successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### [POST] /workspaces/current/endpoints/disable @@ -10037,7 +10037,7 @@ Disable a plugin endpoint | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint disabled successfully | **application/json**: [EndpointDisableResponse](#endpointdisableresponse)
| +| 200 | Endpoint disabled successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### [POST] /workspaces/current/endpoints/enable @@ -10053,7 +10053,7 @@ Enable a plugin endpoint | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint enabled successfully | **application/json**: [EndpointEnableResponse](#endpointenableresponse)
| +| 200 | Endpoint enabled successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### [GET] /workspaces/current/endpoints/list @@ -10087,7 +10087,7 @@ List endpoints for a specific plugin | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [PluginEndpointListResponse](#pluginendpointlistresponse)
| +| 200 | Success | **application/json**: [EndpointListResponse](#endpointlistresponse)
| ### ~~[POST] /workspaces/current/endpoints/update~~ @@ -10105,7 +10105,7 @@ Deprecated legacy alias for updating a plugin endpoint. Use PATCH /workspaces/cu | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint updated successfully | **application/json**: [EndpointUpdateResponse](#endpointupdateresponse)
| +| 200 | Endpoint updated successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### [DELETE] /workspaces/current/endpoints/{id} @@ -10121,7 +10121,7 @@ Delete a plugin endpoint | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint deleted successfully | **application/json**: [EndpointDeleteResponse](#endpointdeleteresponse)
| +| 200 | Endpoint deleted successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### [PATCH] /workspaces/current/endpoints/{id} @@ -10143,7 +10143,7 @@ Update a plugin endpoint | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Endpoint updated successfully | **application/json**: [EndpointUpdateResponse](#endpointupdateresponse)
| +| 200 | Endpoint updated successfully | **application/json**: [SuccessResponse](#successresponse)
| | 403 | Admin privileges required | | ### [GET] /workspaces/current/members @@ -10203,7 +10203,7 @@ Update a plugin endpoint | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [MemberActionTenantResponse](#memberactiontenantresponse)
| +| 200 | Success | **application/json**: [MemberActionResponse](#memberactionresponse)
| ### [POST] /workspaces/current/members/{member_id}/owner-transfer #### Parameters @@ -12471,9 +12471,15 @@ Returns permission flags that control workspace features like member invitations | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [WorkspaceMutationResponse](#workspacemutationresponse)
| +| 200 | Success | **application/json**: [WorkspaceTenantResultResponse](#workspacetenantresultresponse)
| ### [POST] /workspaces/custom-config/webapp-logo/upload +#### Request Body + +| Required | Schema | +| -------- | ------ | +| Yes | **multipart/form-data**: { **"file"**: binary }
| + #### Responses | Code | Description | Schema | @@ -12491,7 +12497,7 @@ Returns permission flags that control workspace features like member invitations | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Success | **application/json**: [WorkspaceMutationResponse](#workspacemutationresponse)
| +| 200 | Success | **application/json**: [WorkspaceTenantResultResponse](#workspacetenantresultresponse)
| ### [POST] /workspaces/switch #### Request Body @@ -16702,12 +16708,6 @@ Model class for provider custom model configuration. | start_node_id | string | | Yes | | start_node_title | string | | Yes | -#### DebugPermission - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| DebugPermission | string | | | - #### DeclaredArrayItem Per-item shape for an ``array``-typed declared output. @@ -17034,12 +17034,6 @@ Request payload for bulk downloading documents as a zip archive. | role | string | | Yes | | token | string | | Yes | -#### EducationActivateResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| EducationActivateResponse | object | | | - #### EducationAutocompleteQuery | Name | Type | Description | Required | @@ -17149,29 +17143,13 @@ Request payload for bulk downloading documents as a zip archive. | plugin_unique_identifier | string | | Yes | | settings | object | | Yes | -#### EndpointCreateResponse +#### EndpointDeclarationResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| success | boolean | Operation success | Yes | - -#### EndpointDeleteResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| success | boolean | Operation success | Yes | - -#### EndpointDisableResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| success | boolean | Operation success | Yes | - -#### EndpointEnableResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| success | boolean | Operation success | Yes | +| hidden | boolean | | No | +| method | string | | Yes | +| path | string | | Yes | #### EndpointIdPayload @@ -17187,6 +17165,23 @@ Request payload for bulk downloading documents as a zip archive. | page_size | integer | | Yes | | plugin_id | string | | Yes | +#### EndpointListItemResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| created_at | dateTime | | Yes | +| declaration | [EndpointProviderDeclarationResponse](#endpointproviderdeclarationresponse) | | No | +| enabled | boolean | | Yes | +| expired_at | dateTime | | Yes | +| hook_id | string | | Yes | +| id | string | | Yes | +| name | string | | Yes | +| plugin_id | string | | Yes | +| settings | object | | Yes | +| tenant_id | string | | Yes | +| updated_at | dateTime | | Yes | +| url | string | | Yes | + #### EndpointListQuery | Name | Type | Description | Required | @@ -17198,7 +17193,59 @@ Request payload for bulk downloading documents as a zip archive. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| endpoints | [ object ] | Endpoint information | Yes | +| endpoints | [ [EndpointListItemResponse](#endpointlistitemresponse) ] | Endpoint information | Yes | + +#### EndpointProviderConfigI18nResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| en_US | string | | Yes | +| ja_JP | string | | No | +| pt_BR | string | | No | +| zh_Hans | string | | No | + +#### EndpointProviderConfigOptionResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| label | [EndpointProviderConfigI18nResponse](#endpointproviderconfigi18nresponse) | | Yes | +| value | string | | Yes | + +#### EndpointProviderConfigResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| default | integer
string
number
boolean | | No | +| help | [EndpointProviderConfigI18nResponse](#endpointproviderconfigi18nresponse) | | No | +| label | [EndpointProviderConfigI18nResponse](#endpointproviderconfigi18nresponse) | | No | +| multiple | boolean | | No | +| name | string | | Yes | +| options | [ [EndpointProviderConfigOptionResponse](#endpointproviderconfigoptionresponse) ] | | No | +| placeholder | [EndpointProviderConfigI18nResponse](#endpointproviderconfigi18nresponse) | | No | +| required | boolean | | No | +| scope | [EndpointProviderConfigScope](#endpointproviderconfigscope) | | No | +| type | [ProviderConfigType](#providerconfigtype) | | Yes | +| url | string | | No | + +#### EndpointProviderConfigScope + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| EndpointProviderConfigScope | string | | | + +#### EndpointProviderDeclarationResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| endpoints | [ [EndpointDeclarationResponse](#endpointdeclarationresponse) ] | | No | +| settings | [ [EndpointProviderConfigResponse](#endpointproviderconfigresponse) ] | | No | + +#### EndpointSettingsPayload + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| name | string | | Yes | +| settings | object | | Yes | #### EndpointUpdatePayload @@ -17207,12 +17254,6 @@ Request payload for bulk downloading documents as a zip archive. | name | string | | Yes | | settings | object | | Yes | -#### EndpointUpdateResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| success | boolean | Operation success | Yes | - #### EnvSuggestion | Name | Type | Description | Required | @@ -18010,12 +18051,6 @@ Input field definition for snippet parameters. | required | boolean | | No | | type | string | | No | -#### InstallPermission - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| InstallPermission | string | | | - #### InstalledAppCreatePayload | Name | Type | Description | Required | @@ -18321,7 +18356,7 @@ Enum class for large language model mode. | marketplace_plugin_unique_identifier | string | | Yes | | version | string | | No | -#### MemberActionTenantResponse +#### MemberActionResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | @@ -19092,13 +19127,13 @@ Enum class for parameter type. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | auto_upgrade | [PluginAutoUpgradeSettingsPayload](#pluginautoupgradesettingspayload) | | Yes | -| category | [PluginCategory](#plugincategory) | | Yes | +| category | [TenantPluginAutoUpgradeCategory](#tenantpluginautoupgradecategory) | | Yes | #### ParserAutoUpgradeFetch | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| category | [PluginCategory](#plugincategory) | | Yes | +| category | [TenantPluginAutoUpgradeCategory](#tenantpluginautoupgradecategory) | | Yes | #### ParserCreateCredential @@ -19196,7 +19231,7 @@ Enum class for parameter type. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| category | [PluginCategory](#plugincategory) | | Yes | +| category | [TenantPluginAutoUpgradeCategory](#tenantpluginautoupgradecategory) | | Yes | | plugin_id | string | | Yes | #### ParserGetCredentials @@ -19284,8 +19319,8 @@ Enum class for parameter type. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| debug_permission | [DebugPermission](#debugpermission) | | No | -| install_permission | [InstallPermission](#installpermission) | | No | +| debug_permission | [TenantPluginDebugPermission](#tenantplugindebugpermission) | | No | +| install_permission | [TenantPluginInstallPermission](#tenantplugininstallpermission) | | No | #### ParserPluginIdentifierQuery @@ -19494,7 +19529,7 @@ Shared permission levels for resources (datasets, credentials, etc.) | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | auto_upgrade | [PluginAutoUpgradeSettingsResponseModel](#pluginautoupgradesettingsresponsemodel) | | Yes | -| category | [PluginCategory](#plugincategory) | | Yes | +| category | [TenantPluginAutoUpgradeCategory](#tenantpluginautoupgradecategory) | | Yes | #### PluginAutoUpgradeSettingsPayload @@ -19502,8 +19537,8 @@ Shared permission levels for resources (datasets, credentials, etc.) | ---- | ---- | ----------- | -------- | | exclude_plugins | [ string ] | | No | | include_plugins | [ string ] | | No | -| strategy_setting | [StrategySetting](#strategysetting) | | No | -| upgrade_mode | [UpgradeMode](#upgrademode) | | No | +| strategy_setting | [TenantPluginAutoUpgradeStrategySetting](#tenantpluginautoupgradestrategysetting) | | No | +| upgrade_mode | [TenantPluginAutoUpgradeMode](#tenantpluginautoupgrademode) | | No | | upgrade_time_of_day | integer | | No | #### PluginAutoUpgradeSettingsResponseModel @@ -19512,8 +19547,8 @@ Shared permission levels for resources (datasets, credentials, etc.) | ---- | ---- | ----------- | -------- | | exclude_plugins | [ string ] | | Yes | | include_plugins | [ string ] | | Yes | -| strategy_setting | [StrategySetting](#strategysetting) | | Yes | -| upgrade_mode | [UpgradeMode](#upgrademode) | | Yes | +| strategy_setting | [TenantPluginAutoUpgradeStrategySetting](#tenantpluginautoupgradestrategysetting) | | Yes | +| upgrade_mode | [TenantPluginAutoUpgradeMode](#tenantpluginautoupgrademode) | | Yes | | upgrade_time_of_day | integer | | Yes | #### PluginCategory @@ -19635,21 +19670,21 @@ Shared permission levels for resources (datasets, credentials, etc.) | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | current_identifier | string | | No | -| type | [Type](#type) | | Yes | +| type | [PluginDependencyType](#plugindependencytype) | | Yes | | value | [Github](#github)
[Marketplace](#marketplace)
[Package](#package) | | Yes | +#### PluginDependencyType + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| PluginDependencyType | string | | | + #### PluginDynamicOptionsResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | options | | | Yes | -#### PluginEndpointListResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| endpoints | [ object ] | Endpoint information | Yes | - #### PluginInstallationItemResponse | Name | Type | Description | Required | @@ -19730,7 +19765,13 @@ Shared permission levels for resources (datasets, credentials, etc.) | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| type | [core__plugin__entities__parameters__PluginParameterAutoGenerate__Type](#core__plugin__entities__parameters__pluginparameterautogenerate__type) | | Yes | +| type | [PluginParameterAutoGenerateType](#pluginparameterautogeneratetype) | | Yes | + +#### PluginParameterAutoGenerateType + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| PluginParameterAutoGenerateType | string | | | #### PluginParameterOption @@ -19750,15 +19791,15 @@ Shared permission levels for resources (datasets, credentials, etc.) | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| debug_permission | [DebugPermission](#debugpermission) | | Yes | -| install_permission | [InstallPermission](#installpermission) | | Yes | +| debug_permission | [TenantPluginDebugPermission](#tenantplugindebugpermission) | | Yes | +| install_permission | [TenantPluginInstallPermission](#tenantplugininstallpermission) | | Yes | #### PluginPermissionSettingsPayload | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| debug_permission | [DebugPermission](#debugpermission) | | No | -| install_permission | [InstallPermission](#installpermission) | | No | +| debug_permission | [TenantPluginDebugPermission](#tenantplugindebugpermission) | | No | +| install_permission | [TenantPluginInstallPermission](#tenantplugininstallpermission) | | No | #### PluginReadmeResponse @@ -19840,9 +19881,15 @@ Model class for common provider settings like credentials | placeholder | [I18nObject](#i18nobject) | | No | | required | boolean | | No | | scope | [AppSelectorScope](#appselectorscope)
[ModelSelectorScope](#modelselectorscope)
[ToolSelectorScope](#toolselectorscope) | | No | -| type | [core__entities__provider_entities__BasicProviderConfig__Type](#core__entities__provider_entities__basicproviderconfig__type) | The type of the credentials | Yes | +| type | [ProviderConfigType](#providerconfigtype) | The type of the credentials | Yes | | url | string | | No | +#### ProviderConfigType + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| ProviderConfigType | string | | | + #### ProviderCredentialResponse | Name | Type | Description | Required | @@ -20998,12 +21045,6 @@ Query parameters for listing snippet published workflows. | paused | integer | | Yes | | success | integer | | Yes | -#### StrategySetting - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| StrategySetting | string | | | - #### StringListSource | Name | Type | Description | Required | @@ -21258,6 +21299,36 @@ Tag type | ---- | ---- | ----------- | -------- | | workspaces | [ [TenantListItemResponse](#tenantlistitemresponse) ] | | Yes | +#### TenantPluginAutoUpgradeCategory + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TenantPluginAutoUpgradeCategory | string | | | + +#### TenantPluginAutoUpgradeMode + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TenantPluginAutoUpgradeMode | string | | | + +#### TenantPluginAutoUpgradeStrategySetting + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TenantPluginAutoUpgradeStrategySetting | string | | | + +#### TenantPluginDebugPermission + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TenantPluginDebugPermission | string | | | + +#### TenantPluginInstallPermission + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| TenantPluginInstallPermission | string | | | + #### TextContentResponse | Name | Type | Description | Required | @@ -21764,12 +21835,6 @@ Enum class for tool provider | ---- | ---- | ----------- | -------- | | TriggerSubscriptionListResponse | array | | | -#### Type - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| Type | string | | | - #### UnaddedModelConfiguration Model class for provider unadded model configuration. @@ -21810,12 +21875,6 @@ Payload for updating a snippet. | icon_info | [IconInfo](#iconinfo) | | No | | name | string | | No | -#### UpgradeMode - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| UpgradeMode | string | | | - #### UploadConfig | Name | Type | Description | Required | @@ -22946,7 +23005,13 @@ Workflow tool configuration | limit | integer,
**Default:** 20 | | No | | page | integer,
**Default:** 1 | | No | -#### WorkspaceListResponse +#### WorkspaceLogoUploadResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| id | string | | Yes | + +#### WorkspacePaginationResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | @@ -22956,19 +23021,6 @@ Workflow tool configuration | page | integer | | Yes | | total | integer | | Yes | -#### WorkspaceLogoUploadResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| id | string | | Yes | - -#### WorkspaceMutationResponse - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| result | string | | Yes | -| tenant | [TenantInfoResponse](#tenantinforesponse) | | Yes | - #### WorkspacePermissionResponse | Name | Type | Description | Required | @@ -22983,6 +23035,13 @@ Workflow tool configuration | ---- | ---- | ----------- | -------- | | permission_keys | [ string ] | | No | +#### WorkspaceTenantResultResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| result | string | | Yes | +| tenant | [TenantInfoResponse](#tenantinforesponse) | | Yes | + #### _AccessControlLanguageQuery | Name | Type | Description | Required | @@ -23051,18 +23110,6 @@ Workflow tool configuration | ---- | ---- | ----------- | -------- | | scope | [RBACResourceWhitelistScope](#rbacresourcewhitelistscope) | | Yes | -#### core__entities__provider_entities__BasicProviderConfig__Type - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| core__entities__provider_entities__BasicProviderConfig__Type | string | | | - -#### core__plugin__entities__parameters__PluginParameterAutoGenerate__Type - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| core__plugin__entities__parameters__PluginParameterAutoGenerate__Type | string | | | - #### core__tools__entities__common_entities__I18nObject Model class for i18n object. diff --git a/api/openapi/markdown/openapi-openapi.md b/api/openapi/markdown/openapi-openapi.md index 08544f20a9d..a649c2fa74f 100644 --- a/api/openapi/markdown/openapi-openapi.md +++ b/api/openapi/markdown/openapi-openapi.md @@ -941,9 +941,15 @@ Strict (extra='forbid'). | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | current_identifier | string | | No | -| type | [Type](#type) | | Yes | +| type | [PluginDependencyType](#plugindependencytype) | | Yes | | value | [Github](#github)
[Marketplace](#marketplace)
[Package](#package) | | Yes | +#### PluginDependencyType + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| PluginDependencyType | string | | | + #### RevokeResponse | Name | Type | Description | Required | @@ -1024,12 +1030,6 @@ types it as a required `'success'` rather than an optional field. | ---- | ---- | ----------- | -------- | | result | string | | Yes | -#### Type - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| Type | string | | | - #### UsageInfo | Name | Type | Description | Required | diff --git a/api/schedule/check_upgradable_plugin_task.py b/api/schedule/check_upgradable_plugin_task.py index f19a2bf18d8..8d913f1eb13 100644 --- a/api/schedule/check_upgradable_plugin_task.py +++ b/api/schedule/check_upgradable_plugin_task.py @@ -8,7 +8,7 @@ from sqlalchemy import select import app from core.helper.marketplace import fetch_global_plugin_manifest from extensions.ext_database import db -from models.account import TenantPluginAutoUpgradeStrategy +from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginAutoUpgradeStrategySetting from tasks import process_tenant_plugin_autoupgrade_check_task as check_task logger = logging.getLogger(__name__) @@ -34,8 +34,7 @@ def check_upgradable_plugin_task(): TenantPluginAutoUpgradeStrategy.upgrade_time_of_day >= now_seconds_of_day, TenantPluginAutoUpgradeStrategy.upgrade_time_of_day < now_seconds_of_day + AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL, - TenantPluginAutoUpgradeStrategy.strategy_setting - != TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED, + TenantPluginAutoUpgradeStrategy.strategy_setting != TenantPluginAutoUpgradeStrategySetting.DISABLED, ) ).all() diff --git a/api/services/account_service.py b/api/services/account_service.py index 445b1acd3b9..9bfa586c457 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -38,6 +38,8 @@ from models.account import ( Tenant, TenantAccountJoin, TenantAccountRole, + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, TenantPluginAutoUpgradeStrategy, TenantStatus, ) @@ -1257,13 +1259,13 @@ class TenantService: session.add(tenant) session.commit() - for category in TenantPluginAutoUpgradeStrategy.PluginCategory: + for category in TenantPluginAutoUpgradeCategory: plugin_upgrade_strategy = TenantPluginAutoUpgradeStrategy( tenant_id=tenant.id, category=category, strategy_setting=PluginAutoUpgradeService.default_strategy_setting_for_category(category), upgrade_time_of_day=PluginAutoUpgradeService.default_upgrade_time_of_day(tenant.id), - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=[], include_plugins=[], ) diff --git a/api/services/plugin/dependencies_analysis.py b/api/services/plugin/dependencies_analysis.py index 2f0c5ae3af5..4c83285ebe5 100644 --- a/api/services/plugin/dependencies_analysis.py +++ b/api/services/plugin/dependencies_analysis.py @@ -2,7 +2,7 @@ import re from configs import dify_config from core.helper import marketplace -from core.plugin.entities.plugin import PluginDependency, PluginInstallationSource +from core.plugin.entities.plugin import PluginDependency, PluginDependencyType, PluginInstallationSource from core.plugin.impl.plugin import PluginInstaller from models.provider_ids import ModelProviderID, ToolProviderID @@ -55,7 +55,7 @@ class DependenciesAnalysisService: unique_identifier = dependency.value.plugin_unique_identifier if unique_identifier in missing_plugin_unique_identifiers: # Extract version for Marketplace dependencies - if dependency.type == PluginDependency.Type.Marketplace: + if dependency.type == PluginDependencyType.Marketplace: version_match = _VERSION_REGEX.search(unique_identifier) if version_match: dependency.value.version = version_match.group("version") @@ -84,7 +84,7 @@ class DependenciesAnalysisService: if plugin.source == PluginInstallationSource.Github: result.append( PluginDependency( - type=PluginDependency.Type.Github, + type=PluginDependencyType.Github, value=PluginDependency.Github( repo=plugin.meta["repo"], version=plugin.meta["version"], @@ -96,7 +96,7 @@ class DependenciesAnalysisService: elif plugin.source == PluginInstallationSource.Marketplace: result.append( PluginDependency( - type=PluginDependency.Type.Marketplace, + type=PluginDependencyType.Marketplace, value=PluginDependency.Marketplace( marketplace_plugin_unique_identifier=plugin.plugin_unique_identifier ), @@ -105,7 +105,7 @@ class DependenciesAnalysisService: elif plugin.source == PluginInstallationSource.Package: result.append( PluginDependency( - type=PluginDependency.Type.Package, + type=PluginDependencyType.Package, value=PluginDependency.Package(plugin_unique_identifier=plugin.plugin_unique_identifier), ) ) @@ -130,7 +130,7 @@ class DependenciesAnalysisService: deps = marketplace.batch_fetch_plugin_manifests(dependencies) return [ PluginDependency( - type=PluginDependency.Type.Marketplace, + type=PluginDependencyType.Marketplace, value=PluginDependency.Marketplace(marketplace_plugin_unique_identifier=dep.latest_package_identifier), ) for dep in deps diff --git a/api/services/plugin/plugin_auto_upgrade_service.py b/api/services/plugin/plugin_auto_upgrade_service.py index 0e13214ee77..f1e1918bdd2 100644 --- a/api/services/plugin/plugin_auto_upgrade_service.py +++ b/api/services/plugin/plugin_auto_upgrade_service.py @@ -14,11 +14,16 @@ from sqlalchemy.orm import Session from core.db.session_factory import session_factory from core.plugin.impl.plugin import PluginInstaller -from models.account import TenantPluginAutoUpgradeStrategy +from models.account import ( + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategy, + TenantPluginAutoUpgradeStrategySetting, +) logger = logging.getLogger(__name__) -PluginCategory = TenantPluginAutoUpgradeStrategy.PluginCategory +PluginCategory = TenantPluginAutoUpgradeCategory PLUGIN_CATEGORIES = tuple(PluginCategory) SECONDS_PER_DAY = 24 * 60 * 60 AUTO_UPGRADE_CHECK_SLOT_SECONDS = 15 * 60 @@ -35,10 +40,10 @@ class PluginAutoUpgradeService: @staticmethod def default_strategy_setting_for_category( category: PluginCategory, - ) -> TenantPluginAutoUpgradeStrategy.StrategySetting: + ) -> TenantPluginAutoUpgradeStrategySetting: if category == PluginCategory.MODEL: - return TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST - return TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY + return TenantPluginAutoUpgradeStrategySetting.LATEST + return TenantPluginAutoUpgradeStrategySetting.FIX_ONLY @staticmethod def default_upgrade_time_of_day(tenant_id: str) -> int: @@ -102,9 +107,9 @@ class PluginAutoUpgradeService: @staticmethod def _has_default_strategy(strategy: TenantPluginAutoUpgradeStrategy) -> bool: return ( - strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY + strategy.strategy_setting == TenantPluginAutoUpgradeStrategySetting.FIX_ONLY and strategy.upgrade_time_of_day == 0 - and strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE + and strategy.upgrade_mode == TenantPluginAutoUpgradeMode.EXCLUDE and not strategy.exclude_plugins and not strategy.include_plugins ) @@ -114,7 +119,7 @@ class PluginAutoUpgradeService: source_strategy: TenantPluginAutoUpgradeStrategy, category: PluginCategory, source_has_default_strategy: bool, - ) -> TenantPluginAutoUpgradeStrategy.StrategySetting: + ) -> TenantPluginAutoUpgradeStrategySetting: # Only pure legacy defaults adopt the new model=latest default. User-edited # strategies keep their original setting across all categories. if source_has_default_strategy: @@ -266,9 +271,9 @@ class PluginAutoUpgradeService: session: Session, tenant_id: str, category: PluginCategory, - strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting, + strategy_setting: TenantPluginAutoUpgradeStrategySetting, upgrade_time_of_day: int, - upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode, + upgrade_mode: TenantPluginAutoUpgradeMode, exclude_plugins: list[str], include_plugins: list[str], ) -> None: @@ -294,9 +299,9 @@ class PluginAutoUpgradeService: @staticmethod def change_strategy( tenant_id: str, - strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting, + strategy_setting: TenantPluginAutoUpgradeStrategySetting, upgrade_time_of_day: int, - upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode, + upgrade_mode: TenantPluginAutoUpgradeMode, exclude_plugins: list[str], include_plugins: list[str], category: PluginCategory, @@ -329,28 +334,28 @@ class PluginAutoUpgradeService: session, tenant_id, category, - TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, 0, - TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + TenantPluginAutoUpgradeMode.EXCLUDE, [plugin_id], [], ) else: - if exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE: + if exist_strategy.upgrade_mode == TenantPluginAutoUpgradeMode.EXCLUDE: # In exclude mode, disabling one plugin means adding it to exclude_plugins. if plugin_id not in exist_strategy.exclude_plugins: new_exclude_plugins = exist_strategy.exclude_plugins.copy() new_exclude_plugins.append(plugin_id) exist_strategy.exclude_plugins = new_exclude_plugins - elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL: + elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeMode.PARTIAL: # In partial mode, disabling one plugin means removing it from include_plugins. if plugin_id in exist_strategy.include_plugins: new_include_plugins = exist_strategy.include_plugins.copy() new_include_plugins.remove(plugin_id) exist_strategy.include_plugins = new_include_plugins - elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL: + elif exist_strategy.upgrade_mode == TenantPluginAutoUpgradeMode.ALL: # In all mode, switch to exclude mode so only this plugin is skipped. - exist_strategy.upgrade_mode = TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE + exist_strategy.upgrade_mode = TenantPluginAutoUpgradeMode.EXCLUDE exist_strategy.exclude_plugins = [plugin_id] @staticmethod diff --git a/api/services/plugin/plugin_permission_service.py b/api/services/plugin/plugin_permission_service.py index 3cca4268d00..19f3de2e52c 100644 --- a/api/services/plugin/plugin_permission_service.py +++ b/api/services/plugin/plugin_permission_service.py @@ -1,7 +1,7 @@ from sqlalchemy import select from core.db.session_factory import session_factory -from models.account import TenantPluginPermission +from models.account import TenantPluginDebugPermission, TenantPluginInstallPermission, TenantPluginPermission class PluginPermissionService: @@ -15,8 +15,8 @@ class PluginPermissionService: @staticmethod def change_permission( tenant_id: str, - install_permission: TenantPluginPermission.InstallPermission, - debug_permission: TenantPluginPermission.DebugPermission, + install_permission: TenantPluginInstallPermission, + debug_permission: TenantPluginDebugPermission, ): with session_factory.create_session() as session, session.begin(): permission = session.scalar( diff --git a/api/services/tools/api_tools_manage_service.py b/api/services/tools/api_tools_manage_service.py index 5ff2c217492..5f7491d1e27 100644 --- a/api/services/tools/api_tools_manage_service.py +++ b/api/services/tools/api_tools_manage_service.py @@ -6,7 +6,7 @@ from httpx import get from sqlalchemy import select from sqlalchemy.orm import sessionmaker -from core.entities.provider_entities import ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.tools.__base.tool_runtime import ToolRuntime from core.tools.custom_tool.provider import ApiToolProviderController from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity @@ -52,7 +52,7 @@ class ApiToolManageService: credentials_schema = [ ProviderConfig( name="auth_type", - type=ProviderConfig.Type.SELECT, + type=ProviderConfigType.SELECT, required=True, default="none", options=[ @@ -63,7 +63,7 @@ class ApiToolManageService: ), ProviderConfig( name="api_key_header", - type=ProviderConfig.Type.TEXT_INPUT, + type=ProviderConfigType.TEXT_INPUT, required=False, placeholder=I18nObject(en_US="Enter api key header", zh_Hans="输入 api key header,如:X-API-KEY"), default="api_key", @@ -71,7 +71,7 @@ class ApiToolManageService: ), ProviderConfig( name="api_key_value", - type=ProviderConfig.Type.TEXT_INPUT, + type=ProviderConfigType.TEXT_INPUT, required=False, placeholder=I18nObject(en_US="Enter api key", zh_Hans="输入 api key"), default="", diff --git a/api/services/tools/mcp_tools_manage_service.py b/api/services/tools/mcp_tools_manage_service.py index 1654ca87868..ae184ba4561 100644 --- a/api/services/tools/mcp_tools_manage_service.py +++ b/api/services/tools/mcp_tools_manage_service.py @@ -459,13 +459,11 @@ class MCPToolManageService: Returns: JSON string of encrypted data """ - from core.entities.provider_entities import BasicProviderConfig + from core.entities.provider_entities import BasicProviderConfig, ProviderConfigType from core.tools.utils.encryption import create_provider_encrypter # Create config for secret fields - config = [ - BasicProviderConfig(type=BasicProviderConfig.Type.SECRET_INPUT, name=field) for field in secret_fields - ] + config = [BasicProviderConfig(type=ProviderConfigType.SECRET_INPUT, name=field) for field in secret_fields] encrypter_instance, _ = create_provider_encrypter( tenant_id=tenant_id, diff --git a/api/tasks/process_tenant_plugin_autoupgrade_check_task.py b/api/tasks/process_tenant_plugin_autoupgrade_check_task.py index 0840a595b7d..a796af5f3d3 100644 --- a/api/tasks/process_tenant_plugin_autoupgrade_check_task.py +++ b/api/tasks/process_tenant_plugin_autoupgrade_check_task.py @@ -11,11 +11,15 @@ from core.plugin.entities.plugin import PluginInstallation, PluginInstallationSo from core.plugin.impl.plugin import PluginInstaller from core.plugin.plugin_service import PluginService from extensions.ext_redis import redis_client -from models.account import TenantPluginAutoUpgradeStrategy +from models.account import ( + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategySetting, +) logger = logging.getLogger(__name__) -PluginCategory = TenantPluginAutoUpgradeStrategy.PluginCategory +PluginCategory = TenantPluginAutoUpgradeCategory RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3 CACHE_REDIS_KEY_PREFIX = "plugin_autoupgrade_check_task:cached_plugin_snapshot:" CACHE_REDIS_TTL = 60 * 60 # 1 hour @@ -95,9 +99,9 @@ def _plugin_matches_category(plugin: PluginInstallation, category: str | None) - @shared_task(queue="plugin") def process_tenant_plugin_autoupgrade_check_task( tenant_id: str, - strategy_setting: TenantPluginAutoUpgradeStrategy.StrategySetting, + strategy_setting: TenantPluginAutoUpgradeStrategySetting, upgrade_time_of_day: int, - upgrade_mode: TenantPluginAutoUpgradeStrategy.UpgradeMode, + upgrade_mode: TenantPluginAutoUpgradeMode, exclude_plugins: list[str], include_plugins: list[str], category: PluginCategory | str | None = None, @@ -113,14 +117,14 @@ def process_tenant_plugin_autoupgrade_check_task( ) ) - if strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED: + if strategy_setting == TenantPluginAutoUpgradeStrategySetting.DISABLED: return # get plugin_ids to check plugin_ids: list[tuple[str, str, str]] = [] # plugin_id, version, unique_identifier click.echo(click.style(f"Upgrade mode: {upgrade_mode}", fg="green")) - if upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL and include_plugins: + if upgrade_mode == TenantPluginAutoUpgradeMode.PARTIAL and include_plugins: all_plugins = manager.list_plugins(tenant_id) for plugin in all_plugins: @@ -137,7 +141,7 @@ def process_tenant_plugin_autoupgrade_check_task( ) ) - elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE: + elif upgrade_mode == TenantPluginAutoUpgradeMode.EXCLUDE: # get all plugins and remove excluded plugins all_plugins = manager.list_plugins(tenant_id) plugin_ids = [ @@ -147,7 +151,7 @@ def process_tenant_plugin_autoupgrade_check_task( and plugin.plugin_id not in exclude_plugins and _plugin_matches_category(plugin, category_value) ] - elif upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL: + elif upgrade_mode == TenantPluginAutoUpgradeMode.ALL: all_plugins = manager.list_plugins(tenant_id) plugin_ids = [ (plugin.plugin_id, plugin.version, plugin.plugin_unique_identifier) @@ -187,8 +191,8 @@ def process_tenant_plugin_autoupgrade_check_task( return False version_checker = { - TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST: operator.ne, - TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY: fix_only_checker, + TenantPluginAutoUpgradeStrategySetting.LATEST: operator.ne, + TenantPluginAutoUpgradeStrategySetting.FIX_ONLY: fix_only_checker, } if version_checker[strategy_setting](latest_version, current_version): diff --git a/api/tests/integration_tests/services/plugin/test_plugin_lifecycle.py b/api/tests/integration_tests/services/plugin/test_plugin_lifecycle.py index fc76129e3c7..23cbdd24b91 100644 --- a/api/tests/integration_tests/services/plugin/test_plugin_lifecycle.py +++ b/api/tests/integration_tests/services/plugin/test_plugin_lifecycle.py @@ -3,11 +3,19 @@ from sqlalchemy import delete, func, select from core.db.session_factory import session_factory from models import Tenant -from models.account import TenantPluginAutoUpgradeStrategy, TenantPluginPermission +from models.account import ( + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategy, + TenantPluginAutoUpgradeStrategySetting, + TenantPluginDebugPermission, + TenantPluginInstallPermission, + TenantPluginPermission, +) from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService from services.plugin.plugin_permission_service import PluginPermissionService -PLUGIN_CATEGORY = TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL +PLUGIN_CATEGORY = TenantPluginAutoUpgradeCategory.TOOL @pytest.fixture @@ -36,31 +44,31 @@ class TestPluginPermissionLifecycle: def test_change_creates_row(self, tenant): result = PluginPermissionService.change_permission( tenant, - TenantPluginPermission.InstallPermission.ADMINS, - TenantPluginPermission.DebugPermission.EVERYONE, + TenantPluginInstallPermission.ADMINS, + TenantPluginDebugPermission.EVERYONE, ) assert result is True perm = PluginPermissionService.get_permission(tenant) assert perm is not None - assert perm.install_permission == TenantPluginPermission.InstallPermission.ADMINS - assert perm.debug_permission == TenantPluginPermission.DebugPermission.EVERYONE + assert perm.install_permission == TenantPluginInstallPermission.ADMINS + assert perm.debug_permission == TenantPluginDebugPermission.EVERYONE def test_change_updates_existing_row(self, tenant): PluginPermissionService.change_permission( tenant, - TenantPluginPermission.InstallPermission.ADMINS, - TenantPluginPermission.DebugPermission.NOBODY, + TenantPluginInstallPermission.ADMINS, + TenantPluginDebugPermission.NOBODY, ) PluginPermissionService.change_permission( tenant, - TenantPluginPermission.InstallPermission.EVERYONE, - TenantPluginPermission.DebugPermission.ADMINS, + TenantPluginInstallPermission.EVERYONE, + TenantPluginDebugPermission.ADMINS, ) perm = PluginPermissionService.get_permission(tenant) assert perm is not None - assert perm.install_permission == TenantPluginPermission.InstallPermission.EVERYONE - assert perm.debug_permission == TenantPluginPermission.DebugPermission.ADMINS + assert perm.install_permission == TenantPluginInstallPermission.EVERYONE + assert perm.debug_permission == TenantPluginDebugPermission.ADMINS with session_factory.create_session() as session: count = session.scalar( @@ -78,9 +86,9 @@ class TestPluginAutoUpgradeLifecycle: def test_change_creates_row(self, tenant): result = PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.LATEST, upgrade_time_of_day=3, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, exclude_plugins=[], include_plugins=[], category=PLUGIN_CATEGORY, @@ -89,24 +97,24 @@ class TestPluginAutoUpgradeLifecycle: strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY) assert strategy is not None - assert strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST + assert strategy.strategy_setting == TenantPluginAutoUpgradeStrategySetting.LATEST assert strategy.upgrade_time_of_day == 3 def test_change_updates_existing_row(self, tenant): PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, exclude_plugins=[], include_plugins=[], category=PLUGIN_CATEGORY, ) PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.LATEST, upgrade_time_of_day=12, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL, + upgrade_mode=TenantPluginAutoUpgradeMode.PARTIAL, exclude_plugins=[], include_plugins=["plugin-a"], category=PLUGIN_CATEGORY, @@ -114,9 +122,9 @@ class TestPluginAutoUpgradeLifecycle: strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY) assert strategy is not None - assert strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST + assert strategy.strategy_setting == TenantPluginAutoUpgradeStrategySetting.LATEST assert strategy.upgrade_time_of_day == 12 - assert strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL + assert strategy.upgrade_mode == TenantPluginAutoUpgradeMode.PARTIAL assert strategy.include_plugins == ["plugin-a"] def test_exclude_plugin_creates_strategy_when_none_exists(self, tenant): @@ -124,15 +132,15 @@ class TestPluginAutoUpgradeLifecycle: strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY) assert strategy is not None - assert strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE + assert strategy.upgrade_mode == TenantPluginAutoUpgradeMode.EXCLUDE assert "my-plugin" in strategy.exclude_plugins def test_exclude_plugin_appends_in_exclude_mode(self, tenant): PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=["existing"], include_plugins=[], category=PLUGIN_CATEGORY, @@ -147,9 +155,9 @@ class TestPluginAutoUpgradeLifecycle: def test_exclude_plugin_dedup_in_exclude_mode(self, tenant): PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=["same-plugin"], include_plugins=[], category=PLUGIN_CATEGORY, @@ -163,9 +171,9 @@ class TestPluginAutoUpgradeLifecycle: def test_exclude_from_partial_mode_removes_from_include(self, tenant): PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL, + upgrade_mode=TenantPluginAutoUpgradeMode.PARTIAL, exclude_plugins=[], include_plugins=["p1", "p2"], category=PLUGIN_CATEGORY, @@ -180,9 +188,9 @@ class TestPluginAutoUpgradeLifecycle: def test_exclude_from_all_mode_switches_to_exclude(self, tenant): PluginAutoUpgradeService.change_strategy( tenant, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.LATEST, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, exclude_plugins=[], include_plugins=[], category=PLUGIN_CATEGORY, @@ -191,5 +199,5 @@ class TestPluginAutoUpgradeLifecycle: strategy = PluginAutoUpgradeService.get_strategy(tenant, PLUGIN_CATEGORY) assert strategy is not None - assert strategy.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE + assert strategy.upgrade_mode == TenantPluginAutoUpgradeMode.EXCLUDE assert "excluded-plugin" in strategy.exclude_plugins diff --git a/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py b/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py index ff1521800ad..b9a0029d131 100644 --- a/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py +++ b/api/tests/test_containers_integration_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py @@ -14,7 +14,7 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_import import ( RagPipelineImportCheckDependenciesApi, RagPipelineImportConfirmApi, ) -from core.plugin.entities.plugin import PluginDependency +from core.plugin.entities.plugin import PluginDependency, PluginDependencyType from models.dataset import Pipeline from services.entities.dsl_entities import CheckDependenciesResult, ImportStatus from services.rag_pipeline.rag_pipeline_dsl_service import RagPipelineImportInfo @@ -237,7 +237,7 @@ class TestRagPipelineImportCheckDependenciesApi: pipeline = MagicMock(spec=Pipeline) dependency = PluginDependency( - type=PluginDependency.Type.Marketplace, + type=PluginDependencyType.Marketplace, value=PluginDependency.Marketplace( marketplace_plugin_unique_identifier="langgenius/example:0.1.0", version="0.1.0", diff --git a/api/tests/test_containers_integration_tests/controllers/console/workspace/test_workspace_wraps.py b/api/tests/test_containers_integration_tests/controllers/console/workspace/test_workspace_wraps.py index 99cabb6cea5..895f638380b 100644 --- a/api/tests/test_containers_integration_tests/controllers/console/workspace/test_workspace_wraps.py +++ b/api/tests/test_containers_integration_tests/controllers/console/workspace/test_workspace_wraps.py @@ -10,7 +10,13 @@ from sqlalchemy.orm import Session from werkzeug.exceptions import Forbidden from controllers.console.workspace import plugin_permission_required -from models.account import Tenant, TenantPluginPermission, TenantStatus +from models.account import ( + Tenant, + TenantPluginDebugPermission, + TenantPluginInstallPermission, + TenantPluginPermission, + TenantStatus, +) def _create_tenant(db_session: Session) -> Tenant: @@ -24,8 +30,8 @@ def _create_tenant(db_session: Session) -> Tenant: def _create_permission( db_session: Session, tenant_id: str, - install: TenantPluginPermission.InstallPermission = TenantPluginPermission.InstallPermission.EVERYONE, - debug: TenantPluginPermission.DebugPermission = TenantPluginPermission.DebugPermission.EVERYONE, + install: TenantPluginInstallPermission = TenantPluginInstallPermission.EVERYONE, + debug: TenantPluginDebugPermission = TenantPluginDebugPermission.EVERYONE, ) -> TenantPluginPermission: perm = TenantPluginPermission( tenant_id=tenant_id, @@ -59,8 +65,8 @@ class TestPluginPermissionRequired: _create_permission( db_session_with_containers, tenant.id, - install=TenantPluginPermission.InstallPermission.NOBODY, - debug=TenantPluginPermission.DebugPermission.EVERYONE, + install=TenantPluginInstallPermission.NOBODY, + debug=TenantPluginDebugPermission.EVERYONE, ) user = SimpleNamespace(is_admin_or_owner=True) @@ -81,8 +87,8 @@ class TestPluginPermissionRequired: _create_permission( db_session_with_containers, tenant.id, - install=TenantPluginPermission.InstallPermission.ADMINS, - debug=TenantPluginPermission.DebugPermission.EVERYONE, + install=TenantPluginInstallPermission.ADMINS, + debug=TenantPluginDebugPermission.EVERYONE, ) user = SimpleNamespace(is_admin_or_owner=False) @@ -103,8 +109,8 @@ class TestPluginPermissionRequired: _create_permission( db_session_with_containers, tenant.id, - install=TenantPluginPermission.InstallPermission.ADMINS, - debug=TenantPluginPermission.DebugPermission.EVERYONE, + install=TenantPluginInstallPermission.ADMINS, + debug=TenantPluginDebugPermission.EVERYONE, ) user = SimpleNamespace(is_admin_or_owner=True) @@ -124,8 +130,8 @@ class TestPluginPermissionRequired: _create_permission( db_session_with_containers, tenant.id, - install=TenantPluginPermission.InstallPermission.EVERYONE, - debug=TenantPluginPermission.DebugPermission.NOBODY, + install=TenantPluginInstallPermission.EVERYONE, + debug=TenantPluginDebugPermission.NOBODY, ) user = SimpleNamespace(is_admin_or_owner=True) @@ -146,8 +152,8 @@ class TestPluginPermissionRequired: _create_permission( db_session_with_containers, tenant.id, - install=TenantPluginPermission.InstallPermission.EVERYONE, - debug=TenantPluginPermission.DebugPermission.ADMINS, + install=TenantPluginInstallPermission.EVERYONE, + debug=TenantPluginDebugPermission.ADMINS, ) user = SimpleNamespace(is_admin_or_owner=False) @@ -168,8 +174,8 @@ class TestPluginPermissionRequired: _create_permission( db_session_with_containers, tenant.id, - install=TenantPluginPermission.InstallPermission.EVERYONE, - debug=TenantPluginPermission.DebugPermission.ADMINS, + install=TenantPluginInstallPermission.EVERYONE, + debug=TenantPluginDebugPermission.ADMINS, ) user = SimpleNamespace(is_admin_or_owner=True) diff --git a/api/tests/test_containers_integration_tests/services/plugin/test_plugin_permission_service.py b/api/tests/test_containers_integration_tests/services/plugin/test_plugin_permission_service.py index e7cf04c0d8b..0a8f49bc7a7 100644 --- a/api/tests/test_containers_integration_tests/services/plugin/test_plugin_permission_service.py +++ b/api/tests/test_containers_integration_tests/services/plugin/test_plugin_permission_service.py @@ -6,7 +6,7 @@ import pytest from sqlalchemy import func, select from sqlalchemy.orm import Session -from models.account import TenantPluginPermission +from models.account import TenantPluginDebugPermission, TenantPluginInstallPermission, TenantPluginPermission from services.plugin.plugin_permission_service import PluginPermissionService @@ -32,8 +32,8 @@ class TestGetPermission: tenant_id = _tenant_id() permission = TenantPluginPermission( tenant_id=tenant_id, - install_permission=TenantPluginPermission.InstallPermission.ADMINS, - debug_permission=TenantPluginPermission.DebugPermission.EVERYONE, + install_permission=TenantPluginInstallPermission.ADMINS, + debug_permission=TenantPluginDebugPermission.EVERYONE, ) db_session_with_containers.add(permission) db_session_with_containers.commit() @@ -43,8 +43,8 @@ class TestGetPermission: assert result is not None assert result.id == permission.id assert result.tenant_id == tenant_id - assert result.install_permission == TenantPluginPermission.InstallPermission.ADMINS - assert result.debug_permission == TenantPluginPermission.DebugPermission.EVERYONE + assert result.install_permission == TenantPluginInstallPermission.ADMINS + assert result.debug_permission == TenantPluginDebugPermission.EVERYONE @pytest.mark.usefixtures("flask_app_with_containers") def test_returns_none_when_not_found(self) -> None: @@ -61,36 +61,36 @@ class TestChangePermission: result = PluginPermissionService.change_permission( tenant_id, - TenantPluginPermission.InstallPermission.EVERYONE, - TenantPluginPermission.DebugPermission.EVERYONE, + TenantPluginInstallPermission.EVERYONE, + TenantPluginDebugPermission.EVERYONE, ) permission = _get_permission(db_session_with_containers, tenant_id) assert result is True assert permission is not None - assert permission.install_permission == TenantPluginPermission.InstallPermission.EVERYONE - assert permission.debug_permission == TenantPluginPermission.DebugPermission.EVERYONE + assert permission.install_permission == TenantPluginInstallPermission.EVERYONE + assert permission.debug_permission == TenantPluginDebugPermission.EVERYONE def test_updates_existing_permission(self, db_session_with_containers: Session) -> None: tenant_id = _tenant_id() existing = TenantPluginPermission( tenant_id=tenant_id, - install_permission=TenantPluginPermission.InstallPermission.EVERYONE, - debug_permission=TenantPluginPermission.DebugPermission.EVERYONE, + install_permission=TenantPluginInstallPermission.EVERYONE, + debug_permission=TenantPluginDebugPermission.EVERYONE, ) db_session_with_containers.add(existing) db_session_with_containers.commit() result = PluginPermissionService.change_permission( tenant_id, - TenantPluginPermission.InstallPermission.ADMINS, - TenantPluginPermission.DebugPermission.ADMINS, + TenantPluginInstallPermission.ADMINS, + TenantPluginDebugPermission.ADMINS, ) permission = _get_permission(db_session_with_containers, tenant_id) assert result is True assert permission is not None assert permission.id == existing.id - assert permission.install_permission == TenantPluginPermission.InstallPermission.ADMINS - assert permission.debug_permission == TenantPluginPermission.DebugPermission.ADMINS + assert permission.install_permission == TenantPluginInstallPermission.ADMINS + assert permission.debug_permission == TenantPluginDebugPermission.ADMINS assert _count_permissions(db_session_with_containers, tenant_id) == 1 diff --git a/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py b/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py index abd9b4facb9..66e6b8fe35a 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_endpoint.py @@ -1,4 +1,5 @@ import inspect +from datetime import UTC, datetime from unittest.mock import patch import pytest @@ -16,9 +17,39 @@ from controllers.console.workspace.endpoint import ( EndpointListApi, EndpointListForSinglePluginApi, ) +from core.entities.provider_entities import ProviderConfig, ProviderConfigType +from core.plugin.entities.endpoint import EndpointEntityWithInstance, EndpointProviderDeclaration from core.plugin.impl.exc import PluginPermissionDeniedError +def _endpoint_entity() -> EndpointEntityWithInstance: + now = datetime(2026, 1, 1, tzinfo=UTC) + return EndpointEntityWithInstance( + id="e1", + created_at=now, + updated_at=now, + tenant_id="t1", + plugin_id="p1", + settings={ + "api_key": "pl********et", + "enabled": True, + "ids": ["a", "b"], + "nested": {"limit": 3}, + }, + expired_at=now, + declaration=EndpointProviderDeclaration( + settings=[ + ProviderConfig(type=ProviderConfigType.SECRET_INPUT, name="api_key"), + ProviderConfig(type=ProviderConfigType.BOOLEAN, name="enabled"), + ] + ), + name="endpoint", + enabled=True, + url="https://example.test/hook-1", + hook_id="hook-1", + ) + + class TestEndpointCollectionApi: def test_create_success(self, app: Flask): api = EndpointCollectionApi() @@ -99,15 +130,40 @@ class TestEndpointListApi: def test_list_success(self, app: Flask): api = EndpointListApi() method = inspect.unwrap(api.get) + endpoint_entity = _endpoint_entity() with ( app.test_request_context("/?page=1&page_size=10"), - patch("controllers.console.workspace.endpoint.EndpointService.list_endpoints", return_value=[{"id": "e1"}]), + patch( + "controllers.console.workspace.endpoint.EndpointService.list_endpoints", + return_value=[endpoint_entity], + ), ): result = method(api, "t1", "u1") - assert "endpoints" in result - assert len(result["endpoints"]) == 1 + endpoint = result["endpoints"][0] + assert endpoint["id"] == "e1" + assert endpoint["created_at"] == "2026-01-01T00:00:00Z" + assert endpoint["updated_at"] == "2026-01-01T00:00:00Z" + assert endpoint["settings"] == { + "api_key": "pl********et", + "enabled": True, + "ids": ["a", "b"], + "nested": {"limit": 3}, + } + assert endpoint["tenant_id"] == "t1" + assert endpoint["plugin_id"] == "p1" + assert endpoint["expired_at"] == "2026-01-01T00:00:00Z" + assert endpoint["declaration"]["settings"][0]["type"] == "secret-input" + assert endpoint["declaration"]["settings"][0]["name"] == "api_key" + assert endpoint["declaration"]["settings"][1]["type"] == "boolean" + assert endpoint["declaration"]["settings"][1]["name"] == "enabled" + assert endpoint["declaration"]["endpoints"] == [] + assert endpoint["name"] == "endpoint" + assert endpoint["enabled"] is True + assert endpoint["url"] == "https://example.test/hook-1" + assert endpoint["hook_id"] == "hook-1" + assert endpoint_entity.settings["api_key"] == "pl********et" def test_list_invalid_query(self, app: Flask): api = EndpointListApi() @@ -129,12 +185,14 @@ class TestEndpointListForSinglePluginApi: app.test_request_context("/?page=1&page_size=10&plugin_id=p1"), patch( "controllers.console.workspace.endpoint.EndpointService.list_endpoints_for_single_plugin", - return_value=[{"id": "e1"}], + return_value=[_endpoint_entity()], ), ): result = method(api, "t1", "u1") - assert "endpoints" in result + assert result["endpoints"][0]["id"] == "e1" + assert result["endpoints"][0]["settings"]["api_key"] == "pl********et" + assert result["endpoints"][0]["settings"]["nested"] == {"limit": 3} def test_list_for_plugin_missing_param(self, app: Flask): api = EndpointListForSinglePluginApi() diff --git a/api/tests/unit_tests/controllers/console/workspace/test_members.py b/api/tests/unit_tests/controllers/console/workspace/test_members.py index 321e2a68f39..4ee31bbdbbe 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_members.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_members.py @@ -130,7 +130,7 @@ class TestMemberInviteEmailApi: features.workspace_members.is_available.return_value = True payload = { - "emails": ["a@test.com"], + "emails": ["A@TEST.com", "a@test.com"], "role": "normal", "language": "en-US", } @@ -138,8 +138,10 @@ class TestMemberInviteEmailApi: with ( app.test_request_context("/", json=payload), patch("controllers.console.workspace.members.FeatureService.get_features", return_value=features), - patch("controllers.console.workspace.members._count_new_member_invites", return_value=1), - patch("controllers.console.workspace.members.RegisterService.invite_new_member", return_value="token"), + patch("controllers.console.workspace.members._count_new_member_invites", return_value=1) as mock_count, + patch( + "controllers.console.workspace.members.RegisterService.invite_new_member", return_value="token" + ) as mock_invite, patch("controllers.console.workspace.members.dify_config.CONSOLE_WEB_URL", "http://x"), patch("controllers.console.workspace.members.dify_config.ENTERPRISE_ENABLED", False), patch("controllers.console.workspace.members.dify_config.BILLING_ENABLED", False), @@ -148,6 +150,10 @@ class TestMemberInviteEmailApi: assert status == 201 assert result["result"] == "success" + assert result["invitation_results"][0]["email"] == "a@test.com" + mock_count.assert_not_called() + mock_invite.assert_called_once() + assert mock_invite.call_args.kwargs["email"] == "a@test.com" def test_invite_limit_exceeded(self, app: Flask): api = MemberInviteEmailApi() diff --git a/api/tests/unit_tests/controllers/console/workspace/test_plugin.py b/api/tests/unit_tests/controllers/console/workspace/test_plugin.py index 2bd6be3bc7a..bbe94fce635 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_plugin.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_plugin.py @@ -44,7 +44,15 @@ from controllers.console.workspace.plugin import ( ) from core.plugin.entities.plugin import PluginInstallation from core.plugin.impl.exc import PluginDaemonClientSideError -from models.account import Account, TenantAccountRole, TenantPluginAutoUpgradeStrategy, TenantPluginPermission +from models.account import ( + Account, + TenantAccountRole, + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategySetting, + TenantPluginDebugPermission, + TenantPluginInstallPermission, +) def _plugin_category_list_item(category: str = "tool") -> dict[str, Any]: @@ -393,8 +401,8 @@ class TestPluginChangePermissionApi: user = _account(TenantAccountRole.NORMAL) payload = { - "install_permission": TenantPluginPermission.InstallPermission.EVERYONE, - "debug_permission": TenantPluginPermission.DebugPermission.EVERYONE, + "install_permission": TenantPluginInstallPermission.EVERYONE, + "debug_permission": TenantPluginDebugPermission.EVERYONE, } with ( @@ -410,8 +418,8 @@ class TestPluginChangePermissionApi: user = _account() payload = { - "install_permission": TenantPluginPermission.InstallPermission.EVERYONE, - "debug_permission": TenantPluginPermission.DebugPermission.EVERYONE, + "install_permission": TenantPluginInstallPermission.EVERYONE, + "debug_permission": TenantPluginDebugPermission.EVERYONE, } with ( @@ -1020,11 +1028,11 @@ class TestPluginChangeAutoUpgradeApi: user = _account() payload = { - "category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value, + "category": TenantPluginAutoUpgradeCategory.TOOL.value, "auto_upgrade": { - "strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + "strategy_setting": TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, "upgrade_time_of_day": 0, - "upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + "upgrade_mode": TenantPluginAutoUpgradeMode.EXCLUDE, "exclude_plugins": [], "include_plugins": [], }, @@ -1048,11 +1056,11 @@ class TestPluginChangeAutoUpgradeApi: user = _account() payload = { - "category": TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL.value, + "category": TenantPluginAutoUpgradeCategory.MODEL.value, "auto_upgrade": { - "strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + "strategy_setting": TenantPluginAutoUpgradeStrategySetting.LATEST, "upgrade_time_of_day": 3600, - "upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + "upgrade_mode": TenantPluginAutoUpgradeMode.ALL, "exclude_plugins": [], "include_plugins": [], }, @@ -1068,7 +1076,7 @@ class TestPluginChangeAutoUpgradeApi: assert result["success"] is True change.assert_called_once() - assert change.call_args.kwargs["category"] == TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL + assert change.call_args.kwargs["category"] == TenantPluginAutoUpgradeCategory.MODEL def test_auto_upgrade_fail(self, app: Flask): api = PluginChangeAutoUpgradeApi() @@ -1077,11 +1085,11 @@ class TestPluginChangeAutoUpgradeApi: user = MagicMock(is_admin_or_owner=True) payload = { - "category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value, + "category": TenantPluginAutoUpgradeCategory.TOOL.value, "auto_upgrade": { - "strategy_setting": TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + "strategy_setting": TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, "upgrade_time_of_day": 0, - "upgrade_mode": TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + "upgrade_mode": TenantPluginAutoUpgradeMode.EXCLUDE, "exclude_plugins": [], "include_plugins": [], }, @@ -1102,16 +1110,16 @@ class TestPluginFetchAutoUpgradeApi: method = unwrap(api.get) auto_upgrade = MagicMock( - category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + category=TenantPluginAutoUpgradeCategory.TOOL, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=1, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=[], include_plugins=[], ) with ( - app.test_request_context(f"/?category={TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value}"), + app.test_request_context(f"/?category={TenantPluginAutoUpgradeCategory.TOOL.value}"), patch( "controllers.console.workspace.plugin.PluginAutoUpgradeService.get_strategy", return_value=auto_upgrade, @@ -1119,7 +1127,7 @@ class TestPluginFetchAutoUpgradeApi: ): result = method(api, "t1") - assert result["category"] == TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL + assert result["category"] == TenantPluginAutoUpgradeCategory.TOOL assert result["auto_upgrade"]["upgrade_time_of_day"] == 1 @@ -1128,7 +1136,7 @@ class TestPluginAutoUpgradeExcludePluginApi: api = PluginAutoUpgradeExcludePluginApi() method = unwrap(api.post) - payload = {"plugin_id": "p", "category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value} + payload = {"plugin_id": "p", "category": TenantPluginAutoUpgradeCategory.TOOL.value} with ( app.test_request_context("/", json=payload), @@ -1142,7 +1150,7 @@ class TestPluginAutoUpgradeExcludePluginApi: api = PluginAutoUpgradeExcludePluginApi() method = unwrap(api.post) - payload = {"plugin_id": "p", "category": TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL.value} + payload = {"plugin_id": "p", "category": TenantPluginAutoUpgradeCategory.TOOL.value} with ( app.test_request_context("/", json=payload), diff --git a/api/tests/unit_tests/controllers/console/workspace/test_workspace.py b/api/tests/unit_tests/controllers/console/workspace/test_workspace.py index b4fb60910e5..28a94387198 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_workspace.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_workspace.py @@ -1,5 +1,6 @@ import inspect import logging +from http import HTTPStatus from io import BytesIO from unittest.mock import MagicMock, patch @@ -26,7 +27,9 @@ from controllers.console.workspace.workspace import ( WebappLogoWorkspaceApi, WorkspaceInfoApi, WorkspaceListApi, + WorkspaceLogoUploadResponse, WorkspacePermissionApi, + WorkspacePermissionResponse, ) from enums.cloud_plan import CloudPlan from libs.datetime_utils import naive_utc_now @@ -99,7 +102,7 @@ class TestTenantListApi: ): result, status = method(api, "t1", user) - assert status == 200 + assert status == HTTPStatus.OK assert len(result["workspaces"]) == 2 assert result["workspaces"][0]["current"] is True assert result["workspaces"][0]["plan"] == CloudPlan.TEAM @@ -146,7 +149,7 @@ class TestTenantListApi: ): result, status = method(api, "t1", user) - assert status == 200 + assert status == HTTPStatus.OK assert result["workspaces"][0]["plan"] == CloudPlan.TEAM assert result["workspaces"][1]["plan"] == CloudPlan.PROFESSIONAL get_plan_bulk_mock.assert_called_once_with(["t1", "t2"]) @@ -192,7 +195,7 @@ class TestTenantListApi: ): result, status = method(api, "t2", user) - assert status == 200 + assert status == HTTPStatus.OK assert result["workspaces"][0]["plan"] == CloudPlan.TEAM assert result["workspaces"][1]["plan"] == CloudPlan.TEAM get_plan_bulk_mock.assert_called_once_with(["t1", "t2"]) @@ -226,7 +229,7 @@ class TestTenantListApi: ): result, status = method(api, "t1", user) - assert status == 200 + assert status == HTTPStatus.OK assert result["workspaces"][0]["plan"] == CloudPlan.SANDBOX get_features_mock.assert_called_once_with("t1", exclude_vector_space=True) @@ -251,7 +254,7 @@ class TestTenantListApi: ): result, status = method(api, "t2", user) - assert status == 200 + assert status == HTTPStatus.OK assert result["workspaces"][0]["plan"] == CloudPlan.SANDBOX assert result["workspaces"][1]["plan"] == CloudPlan.SANDBOX assert result["workspaces"][0]["current"] is False @@ -276,7 +279,7 @@ class TestTenantListApi: ): result, status = method(api, None, user) - assert status == 200 + assert status == HTTPStatus.OK assert result["workspaces"] == [] get_features_mock.assert_not_called() @@ -295,7 +298,7 @@ class TestWorkspaceListApi: ): result, status = method(api) - assert status == 200 + assert status == HTTPStatus.OK assert result["total"] == 1 assert result["has_more"] is False @@ -312,7 +315,7 @@ class TestWorkspaceListApi: ): result, status = method(api) - assert status == 200 + assert status == HTTPStatus.OK assert result["has_more"] is True @@ -332,7 +335,7 @@ class TestTenantApi: ): result, status = method(api, user) - assert status == 200 + assert status == HTTPStatus.OK assert result["id"] == "t1" def test_post_archived_with_switch(self, app: Flask): @@ -386,7 +389,7 @@ class TestTenantApi: result, status = method(api, user) assert "Deprecated URL /info was used." in caplog.messages - assert status == 200 + assert status == HTTPStatus.OK class TestTenantInfoResponse: @@ -586,8 +589,9 @@ class TestWebappLogoWorkspaceApi: result, status = method(api, user) - assert status == 201 - assert result["id"] == "file1" + assert status == HTTPStatus.CREATED + assert result == {"id": "file1"} + assert WorkspaceLogoUploadResponse.model_validate(result).model_dump(mode="json") == {"id": "file1"} def test_filename_missing(self, app: Flask): api = WebappLogoWorkspaceApi() @@ -676,7 +680,7 @@ class TestWorkspaceInfoApi: patch("controllers.console.workspace.workspace.db.session.commit"), patch( "controllers.console.workspace.workspace.WorkspaceService.get_tenant_info", - return_value={"name": "New Name"}, + return_value={"id": "t1", "name": "New Name"}, ), ): result = method(api, "t1") @@ -716,8 +720,14 @@ class TestWorkspacePermissionApi: ): result, status = method(api, "t1") - assert status == 200 - assert result["workspace_id"] == "t1" + assert status == HTTPStatus.OK + expected = { + "workspace_id": "t1", + "allow_member_invite": True, + "allow_owner_transfer": False, + } + assert result == expected + assert WorkspacePermissionResponse.model_validate(result).model_dump(mode="json") == expected def test_no_current_tenant(self, app: Flask): api = WorkspacePermissionApi() diff --git a/api/tests/unit_tests/core/datasource/__base/test_datasource_provider.py b/api/tests/unit_tests/core/datasource/__base/test_datasource_provider.py index 6a3d21a33d1..7e3cc722b0d 100644 --- a/api/tests/unit_tests/core/datasource/__base/test_datasource_provider.py +++ b/api/tests/unit_tests/core/datasource/__base/test_datasource_provider.py @@ -8,7 +8,7 @@ from core.datasource.entities.datasource_entities import ( DatasourceProviderEntityWithPlugin, DatasourceProviderType, ) -from core.entities.provider_entities import ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.tools.errors import ToolProviderCredentialValidationError @@ -149,7 +149,7 @@ class TestDatasourcePluginProviderController: mock_config = MagicMock(spec=ProviderConfig) mock_config.name = "text_field" mock_config.required = True - mock_config.type = ProviderConfig.Type.TEXT_INPUT + mock_config.type = ProviderConfigType.TEXT_INPUT mock_entity = MagicMock(spec=DatasourceProviderEntityWithPlugin) mock_entity.credentials_schema = [mock_config] @@ -167,7 +167,7 @@ class TestDatasourcePluginProviderController: mock_config = MagicMock(spec=ProviderConfig) mock_config.name = "select_field" mock_config.required = True - mock_config.type = ProviderConfig.Type.SELECT + mock_config.type = ProviderConfigType.SELECT mock_config.options = [mock_option] mock_entity = MagicMock(spec=DatasourceProviderEntityWithPlugin) @@ -206,7 +206,7 @@ class TestDatasourcePluginProviderController: mock_config = MagicMock(spec=ProviderConfig) mock_config.name = "valid_field" mock_config.required = True - mock_config.type = ProviderConfig.Type.TEXT_INPUT + mock_config.type = ProviderConfigType.TEXT_INPUT mock_entity = MagicMock(spec=DatasourceProviderEntityWithPlugin) mock_entity.credentials_schema = [mock_config] @@ -243,7 +243,7 @@ class TestDatasourcePluginProviderController: mock_config_text = MagicMock(spec=ProviderConfig) mock_config_text.name = "text_def" mock_config_text.required = False - mock_config_text.type = ProviderConfig.Type.TEXT_INPUT + mock_config_text.type = ProviderConfigType.TEXT_INPUT mock_config_text.default = 123 # Int default, should be converted to str mock_config_other = MagicMock(spec=ProviderConfig) diff --git a/api/tests/unit_tests/core/entities/test_entities_provider_entities.py b/api/tests/unit_tests/core/entities/test_entities_provider_entities.py index a159d3ad4d0..9cf6e5ac3f1 100644 --- a/api/tests/unit_tests/core/entities/test_entities_provider_entities.py +++ b/api/tests/unit_tests/core/entities/test_entities_provider_entities.py @@ -5,6 +5,7 @@ from core.entities.provider_entities import ( BasicProviderConfig, ModelSettings, ProviderConfig, + ProviderConfigType, ProviderQuotaType, ) from core.tools.entities.common_entities import I18nObject @@ -27,22 +28,22 @@ def test_provider_quota_type_value_of_rejects_unknown_values() -> None: def test_basic_provider_config_type_value_of_handles_known_values() -> None: # Arrange / Act - parameter_type = BasicProviderConfig.Type.value_of("text-input") + parameter_type = ProviderConfigType.value_of("text-input") # Assert - assert parameter_type == BasicProviderConfig.Type.TEXT_INPUT + assert parameter_type == ProviderConfigType.TEXT_INPUT def test_basic_provider_config_type_value_of_rejects_invalid_values() -> None: # Arrange / Act / Assert with pytest.raises(ValueError, match="invalid mode value"): - BasicProviderConfig.Type.value_of("unknown") + ProviderConfigType.value_of("unknown") def test_provider_config_to_basic_provider_config_keeps_type_and_name() -> None: # Arrange provider_config = ProviderConfig( - type=BasicProviderConfig.Type.SELECT, + type=ProviderConfigType.SELECT, name="workspace", scope=AppSelectorScope.ALL, options=[ProviderConfig.Option(value="all", label=I18nObject(en_US="All"))], @@ -53,7 +54,7 @@ def test_provider_config_to_basic_provider_config_keeps_type_and_name() -> None: # Assert assert isinstance(basic_config, BasicProviderConfig) - assert basic_config.type == BasicProviderConfig.Type.SELECT + assert basic_config.type == ProviderConfigType.SELECT assert basic_config.name == "workspace" diff --git a/api/tests/unit_tests/core/plugin/test_plugin_manager.py b/api/tests/unit_tests/core/plugin/test_plugin_manager.py index 1c5e1b9c0ca..1aa019254ff 100644 --- a/api/tests/unit_tests/core/plugin/test_plugin_manager.py +++ b/api/tests/unit_tests/core/plugin/test_plugin_manager.py @@ -17,7 +17,7 @@ import pytest from packaging.version import Version from requests import HTTPError -from core.plugin.entities.bundle import PluginBundleDependency +from core.plugin.entities.bundle import PluginBundleDependency, PluginBundleDependencyType from core.plugin.entities.plugin import ( MissingPluginDependency, PluginCategory, @@ -581,11 +581,11 @@ class TestDependencyResolution: bundle_data = b"mock-bundle-data" mock_dependencies = [ PluginBundleDependency( - type=PluginBundleDependency.Type.Marketplace, + type=PluginBundleDependencyType.Marketplace, value=PluginBundleDependency.Marketplace(organization="org1", plugin="plugin1", version="1.0.0"), ), PluginBundleDependency( - type=PluginBundleDependency.Type.Github, + type=PluginBundleDependencyType.Github, value=PluginBundleDependency.Github( repo_address="https://github.com/org/repo", repo="org/repo", @@ -603,8 +603,8 @@ class TestDependencyResolution: # Assert: Verify dependencies were extracted assert len(result) == 2 - assert result[0].type == PluginBundleDependency.Type.Marketplace - assert result[1].type == PluginBundleDependency.Type.Github + assert result[0].type == PluginBundleDependencyType.Marketplace + assert result[1].type == PluginBundleDependencyType.Github mock_request.assert_called_once() def test_fetch_missing_dependencies(self, plugin_installer): @@ -1129,7 +1129,7 @@ class TestPluginBundleOperations: bundle_data = b"mock-marketplace-bundle" mock_dependencies = [ PluginBundleDependency( - type=PluginBundleDependency.Type.Marketplace, + type=PluginBundleDependencyType.Marketplace, value=PluginBundleDependency.Marketplace( organization="langgenius", plugin="search-tool", version="1.2.0" ), @@ -1142,7 +1142,7 @@ class TestPluginBundleOperations: # Assert: Verify marketplace dependency was extracted assert len(result) == 1 - assert result[0].type == PluginBundleDependency.Type.Marketplace + assert result[0].type == PluginBundleDependencyType.Marketplace assert isinstance(result[0].value, PluginBundleDependency.Marketplace) assert result[0].value.organization == "langgenius" assert result[0].value.plugin == "search-tool" @@ -1158,7 +1158,7 @@ class TestPluginBundleOperations: bundle_data = b"mock-github-bundle" mock_dependencies = [ PluginBundleDependency( - type=PluginBundleDependency.Type.Github, + type=PluginBundleDependencyType.Github, value=PluginBundleDependency.Github( repo_address="https://github.com/example/plugin", repo="example/plugin", @@ -1174,7 +1174,7 @@ class TestPluginBundleOperations: # Assert: Verify GitHub dependency was extracted assert len(result) == 1 - assert result[0].type == PluginBundleDependency.Type.Github + assert result[0].type == PluginBundleDependencyType.Github assert isinstance(result[0].value, PluginBundleDependency.Github) assert result[0].value.repo == "example/plugin" assert result[0].value.release == "v2.0.0" @@ -1204,7 +1204,7 @@ class TestPluginBundleOperations: mock_dependencies = [ PluginBundleDependency( - type=PluginBundleDependency.Type.Package, + type=PluginBundleDependencyType.Package, value=PluginBundleDependency.Package( unique_identifier="org/bundled-plugin/1.5.0", manifest=mock_manifest ), @@ -1217,7 +1217,7 @@ class TestPluginBundleOperations: # Assert: Verify package dependency was extracted with manifest assert len(result) == 1 - assert result[0].type == PluginBundleDependency.Type.Package + assert result[0].type == PluginBundleDependencyType.Package assert isinstance(result[0].value, PluginBundleDependency.Package) assert result[0].value.unique_identifier == "org/bundled-plugin/1.5.0" assert result[0].value.manifest.name == "bundled-plugin" @@ -1233,11 +1233,11 @@ class TestPluginBundleOperations: bundle_data = b"mock-mixed-bundle" mock_dependencies = [ PluginBundleDependency( - type=PluginBundleDependency.Type.Marketplace, + type=PluginBundleDependencyType.Marketplace, value=PluginBundleDependency.Marketplace(organization="org1", plugin="plugin1", version="1.0.0"), ), PluginBundleDependency( - type=PluginBundleDependency.Type.Github, + type=PluginBundleDependencyType.Github, value=PluginBundleDependency.Github( repo_address="https://github.com/org2/plugin2", repo="org2/plugin2", @@ -1253,8 +1253,8 @@ class TestPluginBundleOperations: # Assert: Verify all dependency types were extracted assert len(result) == 2 - assert result[0].type == PluginBundleDependency.Type.Marketplace - assert result[1].type == PluginBundleDependency.Type.Github + assert result[0].type == PluginBundleDependencyType.Marketplace + assert result[1].type == PluginBundleDependencyType.Github class TestPluginTaskStatusTransitions: diff --git a/api/tests/unit_tests/core/tools/test_tool_provider_controller.py b/api/tests/unit_tests/core/tools/test_tool_provider_controller.py index 9648305289f..d380eab7a38 100644 --- a/api/tests/unit_tests/core/tools/test_tool_provider_controller.py +++ b/api/tests/unit_tests/core/tools/test_tool_provider_controller.py @@ -5,7 +5,7 @@ from typing import Any, override import pytest -from core.entities.provider_entities import ProviderConfig +from core.entities.provider_entities import ProviderConfig, ProviderConfigType from core.tools.__base.tool import Tool from core.tools.__base.tool_provider import ToolProviderController from core.tools.__base.tool_runtime import ToolRuntime @@ -66,7 +66,7 @@ def _provider_identity() -> ToolProviderIdentity: def test_tool_provider_controller_get_credentials_schema_returns_deep_copy(): entity = ToolProviderEntity( identity=_provider_identity(), - credentials_schema=[ProviderConfig(type=ProviderConfig.Type.TEXT_INPUT, name="api_key", required=False)], + credentials_schema=[ProviderConfig(type=ProviderConfigType.TEXT_INPUT, name="api_key", required=False)], ) controller = _DummyController(entity=entity) @@ -88,10 +88,10 @@ def test_validate_credentials_format_covers_required_default_and_type_rules(): entity = ToolProviderEntity( identity=_provider_identity(), credentials_schema=[ - ProviderConfig(type=ProviderConfig.Type.TEXT_INPUT, name="required_text", required=True), - ProviderConfig(type=ProviderConfig.Type.SECRET_INPUT, name="secret", required=False), - ProviderConfig(type=ProviderConfig.Type.SELECT, name="choice", required=False, options=select_options), - ProviderConfig(type=ProviderConfig.Type.TEXT_INPUT, name="with_default", required=False, default="x"), + ProviderConfig(type=ProviderConfigType.TEXT_INPUT, name="required_text", required=True), + ProviderConfig(type=ProviderConfigType.SECRET_INPUT, name="secret", required=False), + ProviderConfig(type=ProviderConfigType.SELECT, name="choice", required=False, options=select_options), + ProviderConfig(type=ProviderConfigType.TEXT_INPUT, name="with_default", required=False, default="x"), ], ) controller = _DummyController(entity=entity) diff --git a/api/tests/unit_tests/core/tools/utils/test_encryption.py b/api/tests/unit_tests/core/tools/utils/test_encryption.py index ce77473dbdb..d4401488811 100644 --- a/api/tests/unit_tests/core/tools/utils/test_encryption.py +++ b/api/tests/unit_tests/core/tools/utils/test_encryption.py @@ -5,7 +5,7 @@ from unittest.mock import Mock, patch import pytest -from core.entities.provider_entities import BasicProviderConfig +from core.entities.provider_entities import BasicProviderConfig, ProviderConfigType from core.helper.provider_encryption import ProviderConfigEncrypter from core.tools.utils.encryption import create_tool_provider_encrypter @@ -31,7 +31,7 @@ def secret_field() -> BasicProviderConfig: """A SECRET_INPUT field named 'password'.""" return BasicProviderConfig( name="password", - type=BasicProviderConfig.Type.SECRET_INPUT, + type=ProviderConfigType.SECRET_INPUT, ) @@ -40,7 +40,7 @@ def normal_field() -> BasicProviderConfig: """A TEXT_INPUT field named 'username'.""" return BasicProviderConfig( name="username", - type=BasicProviderConfig.Type.TEXT_INPUT, + type=ProviderConfigType.TEXT_INPUT, ) @@ -185,7 +185,7 @@ def test_decrypt_swallow_exception_and_keep_original(encrypter_obj): def test_create_tool_provider_encrypter_builds_cache_and_encrypter(): - basic_config = BasicProviderConfig(name="key", type=BasicProviderConfig.Type.TEXT_INPUT) + basic_config = BasicProviderConfig(name="key", type=ProviderConfigType.TEXT_INPUT) credential_schema_item = SimpleNamespace(to_basic_provider_config=lambda: basic_config) controller = SimpleNamespace( provider_type=SimpleNamespace(value="builtin"), diff --git a/api/tests/unit_tests/services/plugin/test_dependencies_analysis.py b/api/tests/unit_tests/services/plugin/test_dependencies_analysis.py index 8f0886769cf..178dec1bfae 100644 --- a/api/tests/unit_tests/services/plugin/test_dependencies_analysis.py +++ b/api/tests/unit_tests/services/plugin/test_dependencies_analysis.py @@ -11,7 +11,7 @@ from unittest.mock import MagicMock, patch import pytest -from core.plugin.entities.plugin import PluginDependency, PluginInstallationSource +from core.plugin.entities.plugin import PluginDependency, PluginDependencyType, PluginInstallationSource from services.plugin.dependencies_analysis import DependenciesAnalysisService @@ -44,7 +44,7 @@ class TestAnalyzeModelProviderDependency: class TestGetLeakedDependencies: - def _make_dependency(self, identifier: str, dep_type=PluginDependency.Type.Marketplace): + def _make_dependency(self, identifier: str, dep_type=PluginDependencyType.Marketplace): return PluginDependency( type=dep_type, value=PluginDependency.Marketplace(marketplace_plugin_unique_identifier=identifier), @@ -110,7 +110,7 @@ class TestGenerateDependencies: result = DependenciesAnalysisService.generate_dependencies("t1", ["p1"]) assert len(result) == 1 - assert result[0].type == PluginDependency.Type.Github + assert result[0].type == PluginDependencyType.Github assert result[0].value.repo == "org/repo" @patch("services.plugin.dependencies_analysis.PluginInstaller") @@ -120,7 +120,7 @@ class TestGenerateDependencies: result = DependenciesAnalysisService.generate_dependencies("t1", ["p1"]) - assert result[0].type == PluginDependency.Type.Marketplace + assert result[0].type == PluginDependencyType.Marketplace @patch("services.plugin.dependencies_analysis.PluginInstaller") def test_package_source(self, mock_installer_cls): @@ -129,7 +129,7 @@ class TestGenerateDependencies: result = DependenciesAnalysisService.generate_dependencies("t1", ["p1"]) - assert result[0].type == PluginDependency.Type.Package + assert result[0].type == PluginDependencyType.Package @patch("services.plugin.dependencies_analysis.PluginInstaller") def test_remote_source_raises(self, mock_installer_cls): @@ -169,4 +169,4 @@ class TestGenerateLatestDependencies: result = DependenciesAnalysisService.generate_latest_dependencies(["p1"]) assert len(result) == 1 - assert result[0].type == PluginDependency.Type.Marketplace + assert result[0].type == PluginDependencyType.Marketplace diff --git a/api/tests/unit_tests/services/plugin/test_plugin_auto_upgrade_service.py b/api/tests/unit_tests/services/plugin/test_plugin_auto_upgrade_service.py index e284c98079d..0e793fae7ef 100644 --- a/api/tests/unit_tests/services/plugin/test_plugin_auto_upgrade_service.py +++ b/api/tests/unit_tests/services/plugin/test_plugin_auto_upgrade_service.py @@ -4,10 +4,14 @@ from unittest.mock import MagicMock, patch import pytest -from models.account import TenantPluginAutoUpgradeStrategy +from models.account import ( + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategySetting, +) MODULE = "services.plugin.plugin_auto_upgrade_service" -PLUGIN_CATEGORY = TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL +PLUGIN_CATEGORY = TenantPluginAutoUpgradeCategory.TOOL def _patched_session(): @@ -57,9 +61,9 @@ class TestChangeStrategy: result = PluginAutoUpgradeService.change_strategy( "t1", - TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, 3, - TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + TenantPluginAutoUpgradeMode.ALL, [], [], category=PLUGIN_CATEGORY, @@ -78,18 +82,18 @@ class TestChangeStrategy: result = PluginAutoUpgradeService.change_strategy( "t1", - TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + TenantPluginAutoUpgradeStrategySetting.LATEST, 5, - TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL, + TenantPluginAutoUpgradeMode.PARTIAL, ["p1"], ["p2"], category=PLUGIN_CATEGORY, ) assert result is True - assert existing.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST + assert existing.strategy_setting == TenantPluginAutoUpgradeStrategySetting.LATEST assert existing.upgrade_time_of_day == 5 - assert existing.upgrade_mode == TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL + assert existing.upgrade_mode == TenantPluginAutoUpgradeMode.PARTIAL assert existing.exclude_plugins == ["p1"] assert existing.include_plugins == ["p2"] @@ -102,10 +106,8 @@ class TestExcludePlugin: with ( p1, patch(f"{MODULE}.select"), - patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy") as strat_cls, + patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy"), ): - strat_cls.StrategySetting.FIX_ONLY = "fix_only" - strat_cls.UpgradeMode.EXCLUDE = "exclude" from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService result = PluginAutoUpgradeService.exclude_plugin( @@ -120,14 +122,11 @@ class TestExcludePlugin: def test_appends_to_exclude_list_in_exclude_mode(self): p1, session = _patched_session() existing = MagicMock() - existing.upgrade_mode = "exclude" + existing.upgrade_mode = TenantPluginAutoUpgradeMode.EXCLUDE existing.exclude_plugins = ["p-existing"] session.scalar.return_value = existing - with p1, patch(f"{MODULE}.select"), patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy") as strat_cls: - strat_cls.UpgradeMode.EXCLUDE = "exclude" - strat_cls.UpgradeMode.PARTIAL = "partial" - strat_cls.UpgradeMode.ALL = "all" + with p1, patch(f"{MODULE}.select"): from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService result = PluginAutoUpgradeService.exclude_plugin("t1", "p-new", PLUGIN_CATEGORY) @@ -138,14 +137,11 @@ class TestExcludePlugin: def test_removes_from_include_list_in_partial_mode(self): p1, session = _patched_session() existing = MagicMock() - existing.upgrade_mode = "partial" + existing.upgrade_mode = TenantPluginAutoUpgradeMode.PARTIAL existing.include_plugins = ["p1", "p2"] session.scalar.return_value = existing - with p1, patch(f"{MODULE}.select"), patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy") as strat_cls: - strat_cls.UpgradeMode.EXCLUDE = "exclude" - strat_cls.UpgradeMode.PARTIAL = "partial" - strat_cls.UpgradeMode.ALL = "all" + with p1, patch(f"{MODULE}.select"): from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService result = PluginAutoUpgradeService.exclude_plugin("t1", "p1", PLUGIN_CATEGORY) @@ -156,32 +152,26 @@ class TestExcludePlugin: def test_switches_to_exclude_mode_from_all(self): p1, session = _patched_session() existing = MagicMock() - existing.upgrade_mode = "all" + existing.upgrade_mode = TenantPluginAutoUpgradeMode.ALL session.scalar.return_value = existing - with p1, patch(f"{MODULE}.select"), patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy") as strat_cls: - strat_cls.UpgradeMode.EXCLUDE = "exclude" - strat_cls.UpgradeMode.PARTIAL = "partial" - strat_cls.UpgradeMode.ALL = "all" + with p1, patch(f"{MODULE}.select"): from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService result = PluginAutoUpgradeService.exclude_plugin("t1", "p1", PLUGIN_CATEGORY) assert result is True - assert existing.upgrade_mode == "exclude" + assert existing.upgrade_mode == TenantPluginAutoUpgradeMode.EXCLUDE assert existing.exclude_plugins == ["p1"] def test_no_duplicate_in_exclude_list(self): p1, session = _patched_session() existing = MagicMock() - existing.upgrade_mode = "exclude" + existing.upgrade_mode = TenantPluginAutoUpgradeMode.EXCLUDE existing.exclude_plugins = ["p1"] session.scalar.return_value = existing - with p1, patch(f"{MODULE}.select"), patch(f"{MODULE}.TenantPluginAutoUpgradeStrategy") as strat_cls: - strat_cls.UpgradeMode.EXCLUDE = "exclude" - strat_cls.UpgradeMode.PARTIAL = "partial" - strat_cls.UpgradeMode.ALL = "all" + with p1, patch(f"{MODULE}.select"): from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService PluginAutoUpgradeService.exclude_plugin("t1", "p1", PLUGIN_CATEGORY) @@ -193,10 +183,10 @@ class TestBackfillStrategyCategories: def test_creates_default_missing_categories_without_fetching_daemon(self): p1, session = _patched_session() tool_strategy = SimpleNamespace( - category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + category=TenantPluginAutoUpgradeCategory.TOOL, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=[], include_plugins=[], ) @@ -209,17 +199,15 @@ class TestBackfillStrategyCategories: result = PluginAutoUpgradeService.backfill_strategy_categories("t1") expected_time = PluginAutoUpgradeService.default_upgrade_time_of_day("t1") - assert result.created_count == len(TenantPluginAutoUpgradeStrategy.PluginCategory) - 1 + assert result.created_count == len(TenantPluginAutoUpgradeCategory) - 1 assert result.normalized is False installer.list_plugins.assert_not_called() assert tool_strategy.upgrade_time_of_day == expected_time created_strategies = [call.args[0] for call in session.add.call_args_list] model_strategy = next( - strategy - for strategy in created_strategies - if strategy.category == TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL + strategy for strategy in created_strategies if strategy.category == TenantPluginAutoUpgradeCategory.MODEL ) - assert model_strategy.strategy_setting == TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST + assert model_strategy.strategy_setting == TenantPluginAutoUpgradeStrategySetting.LATEST assert model_strategy.upgrade_time_of_day == expected_time def test_default_upgrade_time_is_aligned_to_fifteen_minutes(self): @@ -233,18 +221,18 @@ class TestBackfillStrategyCategories: def test_creates_missing_categories_and_splits_known_plugins(self, caplog: pytest.LogCaptureFixture): p1, session = _patched_session() tool_strategy = SimpleNamespace( - category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + category=TenantPluginAutoUpgradeCategory.TOOL, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=["tool-plugin", "model-plugin", "unknown-plugin"], include_plugins=["model-plugin", "tool-plugin"], ) model_strategy = SimpleNamespace( - category=TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL, - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + category=TenantPluginAutoUpgradeCategory.MODEL, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, upgrade_time_of_day=0, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=["tool-plugin", "model-plugin", "unknown-plugin"], include_plugins=["model-plugin", "tool-plugin"], ) @@ -253,11 +241,11 @@ class TestBackfillStrategyCategories: installed_plugins = [ SimpleNamespace( plugin_id="tool-plugin", - declaration=SimpleNamespace(category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL), + declaration=SimpleNamespace(category=TenantPluginAutoUpgradeCategory.TOOL), ), SimpleNamespace( plugin_id="model-plugin", - declaration=SimpleNamespace(category=TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL), + declaration=SimpleNamespace(category=TenantPluginAutoUpgradeCategory.MODEL), ), ] installer = MagicMock() @@ -272,9 +260,9 @@ class TestBackfillStrategyCategories: result = PluginAutoUpgradeService.backfill_strategy_categories("t1") - assert result.created_count == len(TenantPluginAutoUpgradeStrategy.PluginCategory) - 2 + assert result.created_count == len(TenantPluginAutoUpgradeCategory) - 2 assert result.normalized is True - assert session.add.call_count == len(TenantPluginAutoUpgradeStrategy.PluginCategory) - 2 + assert session.add.call_count == len(TenantPluginAutoUpgradeCategory) - 2 assert tool_strategy.exclude_plugins == ["tool-plugin"] assert tool_strategy.include_plugins == ["tool-plugin"] assert model_strategy.exclude_plugins == ["model-plugin"] diff --git a/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py b/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py index 55ae8144d55..5bb577ba3dc 100644 --- a/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py +++ b/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py @@ -87,11 +87,11 @@ def test_check_dependencies_returns_empty_when_no_redis_data(mocker: MockerFixtu def test_check_dependencies_returns_leaked_deps_from_redis(mocker: MockerFixture) -> None: - from core.plugin.entities.plugin import PluginDependency + from core.plugin.entities.plugin import PluginDependency, PluginDependencyType from services.rag_pipeline.rag_pipeline_dsl_service import CheckDependenciesPendingData dep = PluginDependency( - type=PluginDependency.Type.Marketplace, + type=PluginDependencyType.Marketplace, value=PluginDependency.Marketplace(marketplace_plugin_unique_identifier="test/plugin:0.1.0"), ) pending_data = CheckDependenciesPendingData( @@ -1371,7 +1371,7 @@ def test_confirm_import_fails_when_no_knowledge_index_node(mocker: MockerFixture def test_create_or_update_pipeline_saves_dependencies_to_redis(mocker: MockerFixture) -> None: - from core.plugin.entities.plugin import PluginDependency + from core.plugin.entities.plugin import PluginDependency, PluginDependencyType session = cast(MagicMock, Mock()) service = RagPipelineDslService(session=cast(Session, session)) @@ -1386,7 +1386,7 @@ def test_create_or_update_pipeline_saves_dependencies_to_redis(mocker: MockerFix session.scalar.return_value = None setex = mocker.patch("services.rag_pipeline.rag_pipeline_dsl_service.redis_client.setex") dependency = PluginDependency( - type=PluginDependency.Type.Marketplace, + type=PluginDependencyType.Marketplace, value=PluginDependency.Marketplace(marketplace_plugin_unique_identifier="langgenius/example:0.1.0"), ) diff --git a/api/tests/unit_tests/tasks/test_process_tenant_plugin_autoupgrade_check_task.py b/api/tests/unit_tests/tasks/test_process_tenant_plugin_autoupgrade_check_task.py index a4412c1ee93..d26ae0b5f13 100644 --- a/api/tests/unit_tests/tasks/test_process_tenant_plugin_autoupgrade_check_task.py +++ b/api/tests/unit_tests/tasks/test_process_tenant_plugin_autoupgrade_check_task.py @@ -5,7 +5,11 @@ from unittest.mock import MagicMock, patch from core.plugin.entities.marketplace import MarketplacePluginSnapshot from core.plugin.entities.plugin import PluginCategory, PluginInstallationSource -from models.account import TenantPluginAutoUpgradeStrategy +from models.account import ( + TenantPluginAutoUpgradeCategory, + TenantPluginAutoUpgradeMode, + TenantPluginAutoUpgradeStrategySetting, +) MODULE = "tasks.process_tenant_plugin_autoupgrade_check_task" @@ -41,8 +45,8 @@ def _run_task( *, plugins: list, manifests: list[MarketplacePluginSnapshot], - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.LATEST, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, exclude_plugins=None, include_plugins=None, category=None, @@ -121,9 +125,9 @@ class TestUpgradeCallsMarketplaceService: process_tenant_plugin_autoupgrade_check_task( "tenant-1", - TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + TenantPluginAutoUpgradeStrategySetting.LATEST, 0, - TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + TenantPluginAutoUpgradeMode.ALL, [], [], ) @@ -136,7 +140,7 @@ class TestStrategySetting: upgrade_mock, _ = _run_task( plugins=[_make_plugin("acme/foo", "1.0.0")], manifests=[_make_manifest("acme/foo", "1.0.1")], - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.DISABLED, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.DISABLED, ) upgrade_mock.assert_not_called() @@ -144,7 +148,7 @@ class TestStrategySetting: upgrade_mock, calls = _run_task( plugins=[_make_plugin("acme/foo", "1.0.0")], manifests=[_make_manifest("acme/foo", "1.0.5")], - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, ) upgrade_mock.assert_called_once() assert calls[0][2].endswith(":1.0.5@cafe1234") @@ -153,7 +157,7 @@ class TestStrategySetting: upgrade_mock, _ = _run_task( plugins=[_make_plugin("acme/foo", "1.0.0")], manifests=[_make_manifest("acme/foo", "1.1.0")], - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, ) upgrade_mock.assert_not_called() @@ -161,7 +165,7 @@ class TestStrategySetting: upgrade_mock, _ = _run_task( plugins=[_make_plugin("acme/foo", "1.0.0")], manifests=[_make_manifest("acme/foo", "2.0.0")], - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.FIX_ONLY, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.FIX_ONLY, ) upgrade_mock.assert_not_called() @@ -169,7 +173,7 @@ class TestStrategySetting: upgrade_mock, _ = _run_task( plugins=[_make_plugin("acme/foo", "1.0.0")], manifests=[_make_manifest("acme/foo", "1.0.0")], - strategy_setting=TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + strategy_setting=TenantPluginAutoUpgradeStrategySetting.LATEST, ) upgrade_mock.assert_not_called() @@ -188,7 +192,7 @@ class TestUpgradeMode: upgrade_mock, calls = _run_task( plugins=plugins, manifests=manifests, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, ) assert upgrade_mock.call_count == 2 @@ -208,7 +212,7 @@ class TestUpgradeMode: upgrade_mock, calls = _run_task( plugins=plugins, manifests=manifests, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, ) assert upgrade_mock.call_count == 1 @@ -227,7 +231,7 @@ class TestUpgradeMode: upgrade_mock, calls = _run_task( plugins=plugins, manifests=manifests, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.PARTIAL, + upgrade_mode=TenantPluginAutoUpgradeMode.PARTIAL, include_plugins=["acme/foo"], ) @@ -247,7 +251,7 @@ class TestUpgradeMode: upgrade_mock, calls = _run_task( plugins=plugins, manifests=manifests, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.EXCLUDE, + upgrade_mode=TenantPluginAutoUpgradeMode.EXCLUDE, exclude_plugins=["acme/bar"], ) @@ -267,8 +271,8 @@ class TestUpgradeMode: upgrade_mock, calls = _run_task( plugins=plugins, manifests=manifests, - upgrade_mode=TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, - category=TenantPluginAutoUpgradeStrategy.PluginCategory.MODEL, + upgrade_mode=TenantPluginAutoUpgradeMode.ALL, + category=TenantPluginAutoUpgradeCategory.MODEL, ) upgrade_mock.assert_called_once() @@ -306,9 +310,9 @@ class TestErrorIsolation: process_tenant_plugin_autoupgrade_check_task( "tenant-1", - TenantPluginAutoUpgradeStrategy.StrategySetting.LATEST, + TenantPluginAutoUpgradeStrategySetting.LATEST, 0, - TenantPluginAutoUpgradeStrategy.UpgradeMode.ALL, + TenantPluginAutoUpgradeMode.ALL, [], [], ) diff --git a/packages/contracts/generated/api/console/account/types.gen.ts b/packages/contracts/generated/api/console/account/types.gen.ts index 1a7d1644f8f..bfa12122573 100644 --- a/packages/contracts/generated/api/console/account/types.gen.ts +++ b/packages/contracts/generated/api/console/account/types.gen.ts @@ -87,10 +87,6 @@ export type EducationActivatePayload = { token: string } -export type EducationActivateResponse = { - [key: string]: unknown -} - export type EducationAutocompleteResponse = { curr_page?: number | null data?: Array @@ -301,7 +297,9 @@ export type PostAccountEducationData = { } export type PostAccountEducationResponses = { - 200: EducationActivateResponse + 200: { + [key: string]: unknown + } } export type PostAccountEducationResponse diff --git a/packages/contracts/generated/api/console/account/zod.gen.ts b/packages/contracts/generated/api/console/account/zod.gen.ts index cba539b0316..6805df133b2 100644 --- a/packages/contracts/generated/api/console/account/zod.gen.ts +++ b/packages/contracts/generated/api/console/account/zod.gen.ts @@ -127,11 +127,6 @@ export const zEducationActivatePayload = z.object({ token: z.string(), }) -/** - * EducationActivateResponse - */ -export const zEducationActivateResponse = z.record(z.string(), z.unknown()) - /** * EducationAutocompleteResponse */ @@ -301,7 +296,7 @@ export const zPostAccountEducationBody = zEducationActivatePayload /** * Success */ -export const zPostAccountEducationResponse = zEducationActivateResponse +export const zPostAccountEducationResponse = z.record(z.string(), z.unknown()) export const zGetAccountEducationAutocompleteQuery = z.object({ keywords: z.string(), diff --git a/packages/contracts/generated/api/console/all-workspaces/types.gen.ts b/packages/contracts/generated/api/console/all-workspaces/types.gen.ts index 4683b2d9921..b4c9fa0349a 100644 --- a/packages/contracts/generated/api/console/all-workspaces/types.gen.ts +++ b/packages/contracts/generated/api/console/all-workspaces/types.gen.ts @@ -4,7 +4,7 @@ export type ClientOptions = { baseUrl: `${string}://${string}/console/api` | (string & {}) } -export type WorkspaceListResponse = { +export type WorkspacePaginationResponse = { data: Array has_more: boolean limit: number @@ -30,7 +30,7 @@ export type GetAllWorkspacesData = { } export type GetAllWorkspacesResponses = { - 200: WorkspaceListResponse + 200: WorkspacePaginationResponse } export type GetAllWorkspacesResponse = GetAllWorkspacesResponses[keyof GetAllWorkspacesResponses] diff --git a/packages/contracts/generated/api/console/all-workspaces/zod.gen.ts b/packages/contracts/generated/api/console/all-workspaces/zod.gen.ts index f63bd0e396f..c9cdda11681 100644 --- a/packages/contracts/generated/api/console/all-workspaces/zod.gen.ts +++ b/packages/contracts/generated/api/console/all-workspaces/zod.gen.ts @@ -13,9 +13,9 @@ export const zWorkspaceListItemResponse = z.object({ }) /** - * WorkspaceListResponse + * WorkspacePaginationResponse */ -export const zWorkspaceListResponse = z.object({ +export const zWorkspacePaginationResponse = z.object({ data: z.array(zWorkspaceListItemResponse), has_more: z.boolean(), limit: z.int(), @@ -31,4 +31,4 @@ export const zGetAllWorkspacesQuery = z.object({ /** * Success */ -export const zGetAllWorkspacesResponse = zWorkspaceListResponse +export const zGetAllWorkspacesResponse = zWorkspacePaginationResponse diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index 21473d5bd88..1b5a98eef43 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -1372,7 +1372,7 @@ export type ImportStatus = 'completed' | 'completed-with-warnings' | 'failed' | export type PluginDependency = { current_identifier?: string | null - type: Type + type: PluginDependencyType value: Github | Marketplace | Package } @@ -2186,7 +2186,7 @@ export type ModelConfigPartial = { export type LlmMode = 'chat' | 'completion' -export type Type = 'github' | 'marketplace' | 'package' +export type PluginDependencyType = 'github' | 'marketplace' | 'package' export type Github = { github_plugin_unique_identifier: string diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index 9a928ee68e4..a3d2b9f935a 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -2336,9 +2336,9 @@ export const zConversationDetail = z.object({ }) /** - * Type + * PluginDependencyType */ -export const zType = z.enum(['github', 'marketplace', 'package']) +export const zPluginDependencyType = z.enum(['github', 'marketplace', 'package']) /** * Github @@ -2371,7 +2371,7 @@ export const zPackage = z.object({ */ export const zPluginDependency = z.object({ current_identifier: z.string().nullish(), - type: zType, + type: zPluginDependencyType, value: z.union([zGithub, zMarketplace, zPackage]), }) diff --git a/packages/contracts/generated/api/console/rag/types.gen.ts b/packages/contracts/generated/api/console/rag/types.gen.ts index 5a6c903c383..e760acf8d23 100644 --- a/packages/contracts/generated/api/console/rag/types.gen.ts +++ b/packages/contracts/generated/api/console/rag/types.gen.ts @@ -415,7 +415,7 @@ export type PipelineTemplateItemResponse = { export type PluginDependency = { current_identifier?: string | null - type: Type + type: PluginDependencyType value: Github | Marketplace | Package } @@ -515,7 +515,7 @@ export type DatasetWeightedScoreResponse = { weight_type?: string | null } -export type Type = 'github' | 'marketplace' | 'package' +export type PluginDependencyType = 'github' | 'marketplace' | 'package' export type Github = { github_plugin_unique_identifier: string diff --git a/packages/contracts/generated/api/console/rag/zod.gen.ts b/packages/contracts/generated/api/console/rag/zod.gen.ts index 5edc412926a..54a6b1f9bb1 100644 --- a/packages/contracts/generated/api/console/rag/zod.gen.ts +++ b/packages/contracts/generated/api/console/rag/zod.gen.ts @@ -545,9 +545,9 @@ export const zDatasetRerankingModelResponse = z.object({ }) /** - * Type + * PluginDependencyType */ -export const zType = z.enum(['github', 'marketplace', 'package']) +export const zPluginDependencyType = z.enum(['github', 'marketplace', 'package']) /** * Github @@ -580,7 +580,7 @@ export const zPackage = z.object({ */ export const zPluginDependency = z.object({ current_identifier: z.string().nullish(), - type: zType, + type: zPluginDependencyType, value: z.union([zGithub, zMarketplace, zPackage]), }) diff --git a/packages/contracts/generated/api/console/workspaces/orpc.gen.ts b/packages/contracts/generated/api/console/workspaces/orpc.gen.ts index a5ab691dbd1..12a20a1ca00 100644 --- a/packages/contracts/generated/api/console/workspaces/orpc.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/orpc.gen.ts @@ -376,6 +376,7 @@ import { zPostWorkspacesCurrentTriggerProviderBySubscriptionIdSubscriptionsUpdateResponse, zPostWorkspacesCustomConfigBody, zPostWorkspacesCustomConfigResponse, + zPostWorkspacesCustomConfigWebappLogoUploadBody, zPostWorkspacesCustomConfigWebappLogoUploadResponse, zPostWorkspacesInfoBody, zPostWorkspacesInfoResponse, @@ -4175,6 +4176,7 @@ export const post72 = oc successStatus: 201, tags: ['console'], }) + .input(z.object({ body: zPostWorkspacesCustomConfigWebappLogoUploadBody })) .output(zPostWorkspacesCustomConfigWebappLogoUploadResponse) export const upload2 = { diff --git a/packages/contracts/generated/api/console/workspaces/types.gen.ts b/packages/contracts/generated/api/console/workspaces/types.gen.ts index 3861b005a03..74666443afe 100644 --- a/packages/contracts/generated/api/console/workspaces/types.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/types.gen.ts @@ -133,7 +133,7 @@ export type EndpointCreatePayload = { } } -export type EndpointCreateResponse = { +export type SuccessResponse = { success: boolean } @@ -141,28 +141,8 @@ export type EndpointIdPayload = { endpoint_id: string } -export type EndpointDeleteResponse = { - success: boolean -} - -export type EndpointDisableResponse = { - success: boolean -} - -export type EndpointEnableResponse = { - success: boolean -} - export type EndpointListResponse = { - endpoints: Array<{ - [key: string]: unknown - }> -} - -export type PluginEndpointListResponse = { - endpoints: Array<{ - [key: string]: unknown - }> + endpoints: Array } export type LegacyEndpointUpdatePayload = { @@ -173,10 +153,6 @@ export type LegacyEndpointUpdatePayload = { } } -export type EndpointUpdateResponse = { - success: boolean -} - export type EndpointUpdatePayload = { name: string settings: { @@ -216,7 +192,7 @@ export type SimpleResultDataResponse = { result: string } -export type MemberActionTenantResponse = { +export type MemberActionResponse = { result: string tenant_id: string } @@ -383,7 +359,7 @@ export type BinaryFileResponse = Blob | File export type ParserAutoUpgradeChange = { auto_upgrade: PluginAutoUpgradeSettingsPayload - category: PluginCategory + category: TenantPluginAutoUpgradeCategory } export type PluginAutoUpgradeChangeResponse = { @@ -392,17 +368,13 @@ export type PluginAutoUpgradeChangeResponse = { } export type ParserExcludePlugin = { - category: PluginCategory + category: TenantPluginAutoUpgradeCategory plugin_id: string } -export type SuccessResponse = { - success: boolean -} - export type PluginAutoUpgradeFetchResponse = { auto_upgrade: PluginAutoUpgradeSettingsResponseModel - category: PluginCategory + category: TenantPluginAutoUpgradeCategory } export type PluginDebuggingKeyResponse = { @@ -463,13 +435,13 @@ export type ParserDynamicOptionsWithCredentials = { } export type ParserPermissionChange = { - debug_permission?: DebugPermission - install_permission?: InstallPermission + debug_permission?: TenantPluginDebugPermission + install_permission?: TenantPluginInstallPermission } export type PluginPermissionResponse = { - debug_permission: DebugPermission - install_permission: InstallPermission + debug_permission: TenantPluginDebugPermission + install_permission: TenantPluginInstallPermission } export type PluginReadmeResponse = { @@ -913,7 +885,7 @@ export type WorkspaceCustomConfigPayload = { replace_webapp_logo?: string | null } -export type WorkspaceMutationResponse = { +export type WorkspaceTenantResultResponse = { result: string tenant: TenantInfoResponse } @@ -1005,7 +977,7 @@ export type ImportStatus = 'completed' | 'completed-with-warnings' | 'failed' | export type PluginDependency = { current_identifier?: string | null - type: Type + type: PluginDependencyType value: Github | Marketplace | Package } @@ -1037,6 +1009,23 @@ export type Inner = { provider?: string | null } +export type EndpointListItemResponse = { + created_at: string + declaration?: EndpointProviderDeclarationResponse + enabled: boolean + expired_at: string + hook_id: string + id: string + name: string + plugin_id: string + settings: { + [key: string]: unknown + } + tenant_id: string + updated_at: string + url: string +} + export type MemberInviteResultResponse = { email: string message?: string | null @@ -1126,12 +1115,12 @@ export type ProviderWithModelsResponse = { export type PluginAutoUpgradeSettingsPayload = { exclude_plugins?: Array include_plugins?: Array - strategy_setting?: StrategySetting - upgrade_mode?: UpgradeMode + strategy_setting?: TenantPluginAutoUpgradeStrategySetting + upgrade_mode?: TenantPluginAutoUpgradeMode upgrade_time_of_day?: number } -export type PluginCategory +export type TenantPluginAutoUpgradeCategory = | 'agent-strategy' | 'datasource' | 'extension' @@ -1142,8 +1131,8 @@ export type PluginCategory export type PluginAutoUpgradeSettingsResponseModel = { exclude_plugins: Array include_plugins: Array - strategy_setting: StrategySetting - upgrade_mode: UpgradeMode + strategy_setting: TenantPluginAutoUpgradeStrategySetting + upgrade_mode: TenantPluginAutoUpgradeMode upgrade_time_of_day: number } @@ -1175,9 +1164,9 @@ export type LatestPluginCache = { version: string } -export type DebugPermission = 'admins' | 'everyone' | 'noone' +export type TenantPluginDebugPermission = 'admins' | 'everyone' | 'noone' -export type InstallPermission = 'admins' | 'everyone' | 'noone' +export type TenantPluginInstallPermission = 'admins' | 'everyone' | 'noone' export type PluginCategoryBuiltinToolProviderResponse = { allow_delete: boolean @@ -1350,7 +1339,7 @@ export type ProviderConfig = { placeholder?: I18nObject | null required?: boolean scope?: AppSelectorScope | ModelSelectorScope | ToolSelectorScope | null - type: CoreEntitiesProviderEntitiesBasicProviderConfigType + type: ProviderConfigType url?: string | null } @@ -1407,7 +1396,7 @@ export type TriggerProviderSubscriptionApiEntity = { workflows_in_use: number } -export type Type = 'github' | 'marketplace' | 'package' +export type PluginDependencyType = 'github' | 'marketplace' | 'package' export type Github = { github_plugin_unique_identifier: string @@ -1437,6 +1426,11 @@ export type SimpleProviderEntityResponse = { tenant_id: string } +export type EndpointProviderDeclarationResponse = { + endpoints?: Array | null + settings?: Array +} + export type ConfigurateMethod = 'customizable-model' | 'predefined-model' export type CustomConfigurationResponse = { @@ -1529,9 +1523,9 @@ export type ProviderModelWithStatusEntity = { export type CustomConfigurationStatus = 'active' | 'no-configure' -export type StrategySetting = 'disabled' | 'fix_only' | 'latest' +export type TenantPluginAutoUpgradeStrategySetting = 'disabled' | 'fix_only' | 'latest' -export type UpgradeMode = 'all' | 'exclude' | 'partial' +export type TenantPluginAutoUpgradeMode = 'all' | 'exclude' | 'partial' export type PluginDeclarationResponse = { agent_strategy?: { @@ -1673,7 +1667,7 @@ export type ModelSelectorScope export type ToolSelectorScope = 'all' | 'builtin' | 'custom' | 'workflow' -export type CoreEntitiesProviderEntitiesBasicProviderConfigType +export type ProviderConfigType = | 'app-selector' | 'array[tools]' | 'boolean' @@ -1701,6 +1695,26 @@ export type AiModelEntityResponse = { pricing?: PriceConfigResponse | null } +export type EndpointDeclarationResponse = { + hidden?: boolean + method: string + path: string +} + +export type EndpointProviderConfigResponse = { + default?: number | string | number | boolean | null + help?: EndpointProviderConfigI18nResponse | null + label?: EndpointProviderConfigI18nResponse | null + multiple?: boolean + name: string + options?: Array | null + placeholder?: EndpointProviderConfigI18nResponse | null + required?: boolean + scope?: EndpointProviderConfigScope | null + type: ProviderConfigType + url?: string | null +} + export type UnaddedModelConfiguration = { model: string model_type: ModelType @@ -1746,6 +1760,14 @@ export type QuotaConfiguration = { restrict_models?: Array } +export type PluginCategory + = | 'agent-strategy' + | 'datasource' + | 'extension' + | 'model' + | 'tool' + | 'trigger' + export type ProviderEntityResponse = { background?: string | null configurate_methods: Array @@ -1766,7 +1788,7 @@ export type ProviderEntityResponse = { } export type PluginParameterAutoGenerate = { - type: CorePluginEntitiesParametersPluginParameterAutoGenerateType + type: PluginParameterAutoGenerateType } export type PluginParameterOption = { @@ -1800,6 +1822,33 @@ export type PriceConfigResponse = { unit: string } +export type EndpointProviderConfigI18nResponse = { + en_US: string + ja_JP?: string | null + pt_BR?: string | null + zh_Hans?: string | null +} + +export type EndpointProviderConfigOptionResponse = { + label: EndpointProviderConfigI18nResponse + value: string +} + +export type EndpointProviderConfigScope + = | 'all' + | 'builtin' + | 'chat' + | 'completion' + | 'custom' + | 'llm' + | 'moderation' + | 'rerank' + | 'speech2text' + | 'text-embedding' + | 'tts' + | 'vision' + | 'workflow' + export type FormOption = { label: GraphonModelRuntimeEntitiesCommonEntitiesI18nObject show_on?: Array @@ -1821,7 +1870,7 @@ export type RestrictModel = { model_type: ModelType } -export type CorePluginEntitiesParametersPluginParameterAutoGenerateType = 'prompt_instruction' +export type PluginParameterAutoGenerateType = 'prompt_instruction' export type AccountWithRoleListResponseWritable = { accounts: Array @@ -2156,7 +2205,7 @@ export type PostWorkspacesCurrentEndpointsErrors = { } export type PostWorkspacesCurrentEndpointsResponses = { - 200: EndpointCreateResponse + 200: SuccessResponse } export type PostWorkspacesCurrentEndpointsResponse @@ -2174,7 +2223,7 @@ export type PostWorkspacesCurrentEndpointsCreateErrors = { } export type PostWorkspacesCurrentEndpointsCreateResponses = { - 200: EndpointCreateResponse + 200: SuccessResponse } export type PostWorkspacesCurrentEndpointsCreateResponse @@ -2192,7 +2241,7 @@ export type PostWorkspacesCurrentEndpointsDeleteErrors = { } export type PostWorkspacesCurrentEndpointsDeleteResponses = { - 200: EndpointDeleteResponse + 200: SuccessResponse } export type PostWorkspacesCurrentEndpointsDeleteResponse @@ -2210,7 +2259,7 @@ export type PostWorkspacesCurrentEndpointsDisableErrors = { } export type PostWorkspacesCurrentEndpointsDisableResponses = { - 200: EndpointDisableResponse + 200: SuccessResponse } export type PostWorkspacesCurrentEndpointsDisableResponse @@ -2228,7 +2277,7 @@ export type PostWorkspacesCurrentEndpointsEnableErrors = { } export type PostWorkspacesCurrentEndpointsEnableResponses = { - 200: EndpointEnableResponse + 200: SuccessResponse } export type PostWorkspacesCurrentEndpointsEnableResponse @@ -2263,7 +2312,7 @@ export type GetWorkspacesCurrentEndpointsListPluginData = { } export type GetWorkspacesCurrentEndpointsListPluginResponses = { - 200: PluginEndpointListResponse + 200: EndpointListResponse } export type GetWorkspacesCurrentEndpointsListPluginResponse @@ -2281,7 +2330,7 @@ export type PostWorkspacesCurrentEndpointsUpdateErrors = { } export type PostWorkspacesCurrentEndpointsUpdateResponses = { - 200: EndpointUpdateResponse + 200: SuccessResponse } export type PostWorkspacesCurrentEndpointsUpdateResponse @@ -2301,7 +2350,7 @@ export type DeleteWorkspacesCurrentEndpointsByIdErrors = { } export type DeleteWorkspacesCurrentEndpointsByIdResponses = { - 200: EndpointDeleteResponse + 200: SuccessResponse } export type DeleteWorkspacesCurrentEndpointsByIdResponse @@ -2321,7 +2370,7 @@ export type PatchWorkspacesCurrentEndpointsByIdErrors = { } export type PatchWorkspacesCurrentEndpointsByIdResponses = { - 200: EndpointUpdateResponse + 200: SuccessResponse } export type PatchWorkspacesCurrentEndpointsByIdResponse @@ -2393,7 +2442,7 @@ export type DeleteWorkspacesCurrentMembersByMemberIdData = { } export type DeleteWorkspacesCurrentMembersByMemberIdResponses = { - 200: MemberActionTenantResponse + 200: MemberActionResponse } export type DeleteWorkspacesCurrentMembersByMemberIdResponse @@ -4936,14 +4985,16 @@ export type PostWorkspacesCustomConfigData = { } export type PostWorkspacesCustomConfigResponses = { - 200: WorkspaceMutationResponse + 200: WorkspaceTenantResultResponse } export type PostWorkspacesCustomConfigResponse = PostWorkspacesCustomConfigResponses[keyof PostWorkspacesCustomConfigResponses] export type PostWorkspacesCustomConfigWebappLogoUploadData = { - body?: never + body: { + file: Blob | File + } path?: never query?: never url: '/workspaces/custom-config/webapp-logo/upload' @@ -4964,7 +5015,7 @@ export type PostWorkspacesInfoData = { } export type PostWorkspacesInfoResponses = { - 200: WorkspaceMutationResponse + 200: WorkspaceTenantResultResponse } export type PostWorkspacesInfoResponse diff --git a/packages/contracts/generated/api/console/workspaces/zod.gen.ts b/packages/contracts/generated/api/console/workspaces/zod.gen.ts index b27187dfddd..7057bd23bef 100644 --- a/packages/contracts/generated/api/console/workspaces/zod.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/zod.gen.ts @@ -56,9 +56,9 @@ export const zEndpointCreatePayload = z.object({ }) /** - * EndpointCreateResponse + * SuccessResponse */ -export const zEndpointCreateResponse = z.object({ +export const zSuccessResponse = z.object({ success: z.boolean(), }) @@ -69,41 +69,6 @@ export const zEndpointIdPayload = z.object({ endpoint_id: z.string(), }) -/** - * EndpointDeleteResponse - */ -export const zEndpointDeleteResponse = z.object({ - success: z.boolean(), -}) - -/** - * EndpointDisableResponse - */ -export const zEndpointDisableResponse = z.object({ - success: z.boolean(), -}) - -/** - * EndpointEnableResponse - */ -export const zEndpointEnableResponse = z.object({ - success: z.boolean(), -}) - -/** - * EndpointListResponse - */ -export const zEndpointListResponse = z.object({ - endpoints: z.array(z.record(z.string(), z.unknown())), -}) - -/** - * PluginEndpointListResponse - */ -export const zPluginEndpointListResponse = z.object({ - endpoints: z.array(z.record(z.string(), z.unknown())), -}) - /** * LegacyEndpointUpdatePayload */ @@ -113,13 +78,6 @@ export const zLegacyEndpointUpdatePayload = z.object({ settings: z.record(z.string(), z.unknown()), }) -/** - * EndpointUpdateResponse - */ -export const zEndpointUpdateResponse = z.object({ - success: z.boolean(), -}) - /** * EndpointUpdatePayload */ @@ -170,9 +128,9 @@ export const zSimpleResultDataResponse = z.object({ }) /** - * MemberActionTenantResponse + * MemberActionResponse */ -export const zMemberActionTenantResponse = z.object({ +export const zMemberActionResponse = z.object({ result: z.string(), tenant_id: z.string(), }) @@ -296,13 +254,6 @@ export const zPluginAutoUpgradeChangeResponse = z.object({ success: z.boolean(), }) -/** - * SuccessResponse - */ -export const zSuccessResponse = z.object({ - success: z.boolean(), -}) - /** * PluginDebuggingKeyResponse */ @@ -724,9 +675,9 @@ export const zTenantInfoResponse = z.object({ }) /** - * WorkspaceMutationResponse + * WorkspaceTenantResultResponse */ -export const zWorkspaceMutationResponse = z.object({ +export const zWorkspaceTenantResultResponse = z.object({ result: z.string(), tenant: zTenantInfoResponse, }) @@ -1073,9 +1024,9 @@ export const zModelCredentialResponse = z.object({ }) /** - * PluginCategory + * TenantPluginAutoUpgradeCategory */ -export const zPluginCategory = z.enum([ +export const zTenantPluginAutoUpgradeCategory = z.enum([ 'agent-strategy', 'datasource', 'extension', @@ -1088,7 +1039,7 @@ export const zPluginCategory = z.enum([ * ParserExcludePlugin */ export const zParserExcludePlugin = z.object({ - category: zPluginCategory, + category: zTenantPluginAutoUpgradeCategory, plugin_id: z.string(), }) @@ -1112,29 +1063,29 @@ export const zPluginVersionsResponse = z.object({ }) /** - * DebugPermission + * TenantPluginDebugPermission */ -export const zDebugPermission = z.enum(['admins', 'everyone', 'noone']) +export const zTenantPluginDebugPermission = z.enum(['admins', 'everyone', 'noone']) /** - * InstallPermission + * TenantPluginInstallPermission */ -export const zInstallPermission = z.enum(['admins', 'everyone', 'noone']) +export const zTenantPluginInstallPermission = z.enum(['admins', 'everyone', 'noone']) /** * ParserPermissionChange */ export const zParserPermissionChange = z.object({ - debug_permission: zDebugPermission.optional().default('everyone'), - install_permission: zInstallPermission.optional().default('everyone'), + debug_permission: zTenantPluginDebugPermission.optional().default('everyone'), + install_permission: zTenantPluginInstallPermission.optional().default('everyone'), }) /** * PluginPermissionResponse */ export const zPluginPermissionResponse = z.object({ - debug_permission: zDebugPermission, - install_permission: zInstallPermission, + debug_permission: zTenantPluginDebugPermission, + install_permission: zTenantPluginInstallPermission, }) /** @@ -1492,9 +1443,9 @@ export const zTriggerProviderSubscriptionApiEntity = z.object({ export const zTriggerSubscriptionListResponse = z.array(zTriggerProviderSubscriptionApiEntity) /** - * Type + * PluginDependencyType */ -export const zType = z.enum(['github', 'marketplace', 'package']) +export const zPluginDependencyType = z.enum(['github', 'marketplace', 'package']) /** * Github @@ -1527,7 +1478,7 @@ export const zPackage = z.object({ */ export const zPluginDependency = z.object({ current_identifier: z.string().nullish(), - type: zType, + type: zPluginDependencyType, value: z.union([zGithub, zMarketplace, zPackage]), }) @@ -1709,14 +1660,14 @@ export const zProviderWithModelsDataResponse = z.object({ }) /** - * StrategySetting + * TenantPluginAutoUpgradeStrategySetting */ -export const zStrategySetting = z.enum(['disabled', 'fix_only', 'latest']) +export const zTenantPluginAutoUpgradeStrategySetting = z.enum(['disabled', 'fix_only', 'latest']) /** - * UpgradeMode + * TenantPluginAutoUpgradeMode */ -export const zUpgradeMode = z.enum(['all', 'exclude', 'partial']) +export const zTenantPluginAutoUpgradeMode = z.enum(['all', 'exclude', 'partial']) /** * PluginAutoUpgradeSettingsPayload @@ -1724,8 +1675,8 @@ export const zUpgradeMode = z.enum(['all', 'exclude', 'partial']) export const zPluginAutoUpgradeSettingsPayload = z.object({ exclude_plugins: z.array(z.string()).optional(), include_plugins: z.array(z.string()).optional(), - strategy_setting: zStrategySetting.optional().default('fix_only'), - upgrade_mode: zUpgradeMode.optional().default('exclude'), + strategy_setting: zTenantPluginAutoUpgradeStrategySetting.optional().default('fix_only'), + upgrade_mode: zTenantPluginAutoUpgradeMode.optional().default('exclude'), upgrade_time_of_day: z.int().optional().default(0), }) @@ -1734,7 +1685,7 @@ export const zPluginAutoUpgradeSettingsPayload = z.object({ */ export const zParserAutoUpgradeChange = z.object({ auto_upgrade: zPluginAutoUpgradeSettingsPayload, - category: zPluginCategory, + category: zTenantPluginAutoUpgradeCategory, }) /** @@ -1743,8 +1694,8 @@ export const zParserAutoUpgradeChange = z.object({ export const zPluginAutoUpgradeSettingsResponseModel = z.object({ exclude_plugins: z.array(z.string()), include_plugins: z.array(z.string()), - strategy_setting: zStrategySetting, - upgrade_mode: zUpgradeMode, + strategy_setting: zTenantPluginAutoUpgradeStrategySetting, + upgrade_mode: zTenantPluginAutoUpgradeMode, upgrade_time_of_day: z.int(), }) @@ -1753,7 +1704,7 @@ export const zPluginAutoUpgradeSettingsResponseModel = z.object({ */ export const zPluginAutoUpgradeFetchResponse = z.object({ auto_upgrade: zPluginAutoUpgradeSettingsResponseModel, - category: zPluginCategory, + category: zTenantPluginAutoUpgradeCategory, }) /** @@ -1988,9 +1939,9 @@ export const zModelSelectorScope = z.enum([ export const zToolSelectorScope = z.enum(['all', 'builtin', 'custom', 'workflow']) /** - * Type + * ProviderConfigType */ -export const zCoreEntitiesProviderEntitiesBasicProviderConfigType = z.enum([ +export const zProviderConfigType = z.enum([ 'app-selector', 'array[tools]', 'boolean', @@ -2015,7 +1966,7 @@ export const zProviderConfig = z.object({ placeholder: zI18nObject.nullish(), required: z.boolean().optional().default(false), scope: z.union([zAppSelectorScope, zModelSelectorScope, zToolSelectorScope]).nullish(), - type: zCoreEntitiesProviderEntitiesBasicProviderConfigType, + type: zProviderConfigType, url: z.string().nullish(), }) @@ -2075,6 +2026,15 @@ export const zTriggerOAuthClientResponse = z.object({ system_configured: z.boolean(), }) +/** + * EndpointDeclarationResponse + */ +export const zEndpointDeclarationResponse = z.object({ + hidden: z.boolean().optional().default(false), + method: z.string(), + path: z.string(), +}) + /** * UnaddedModelConfiguration * @@ -2127,6 +2087,18 @@ export const zFieldModelSchema = z.object({ */ export const zProviderQuotaType = z.enum(['free', 'paid', 'trial']) +/** + * PluginCategory + */ +export const zPluginCategory = z.enum([ + 'agent-strategy', + 'datasource', + 'extension', + 'model', + 'tool', + 'trigger', +]) + /** * PluginParameterOption */ @@ -2253,6 +2225,93 @@ export const zModelWithProviderListResponse = z.object({ data: z.array(zModelWithProviderEntityResponse), }) +/** + * EndpointProviderConfigI18nResponse + */ +export const zEndpointProviderConfigI18nResponse = z.object({ + en_US: z.string(), + ja_JP: z.string().nullish(), + pt_BR: z.string().nullish(), + zh_Hans: z.string().nullish(), +}) + +/** + * EndpointProviderConfigOptionResponse + */ +export const zEndpointProviderConfigOptionResponse = z.object({ + label: zEndpointProviderConfigI18nResponse, + value: z.string(), +}) + +/** + * EndpointProviderConfigScope + */ +export const zEndpointProviderConfigScope = z.enum([ + 'all', + 'builtin', + 'chat', + 'completion', + 'custom', + 'llm', + 'moderation', + 'rerank', + 'speech2text', + 'text-embedding', + 'tts', + 'vision', + 'workflow', +]) + +/** + * EndpointProviderConfigResponse + */ +export const zEndpointProviderConfigResponse = z.object({ + default: z.union([z.int(), z.string(), z.number(), z.boolean()]).nullish(), + help: zEndpointProviderConfigI18nResponse.nullish(), + label: zEndpointProviderConfigI18nResponse.nullish(), + multiple: z.boolean().optional().default(false), + name: z.string(), + options: z.array(zEndpointProviderConfigOptionResponse).nullish(), + placeholder: zEndpointProviderConfigI18nResponse.nullish(), + required: z.boolean().optional().default(false), + scope: zEndpointProviderConfigScope.nullish(), + type: zProviderConfigType, + url: z.string().nullish(), +}) + +/** + * EndpointProviderDeclarationResponse + */ +export const zEndpointProviderDeclarationResponse = z.object({ + endpoints: z.array(zEndpointDeclarationResponse).nullish(), + settings: z.array(zEndpointProviderConfigResponse).optional(), +}) + +/** + * EndpointListItemResponse + */ +export const zEndpointListItemResponse = z.object({ + created_at: z.iso.datetime(), + declaration: zEndpointProviderDeclarationResponse.optional(), + enabled: z.boolean(), + expired_at: z.iso.datetime(), + hook_id: z.string(), + id: z.string(), + name: z.string(), + plugin_id: z.string(), + settings: z.record(z.string(), z.unknown()), + tenant_id: z.string(), + updated_at: z.iso.datetime(), + url: z.string(), +}) + +/** + * EndpointListResponse + */ +export const zEndpointListResponse = z.object({ + endpoints: z.array(zEndpointListItemResponse), +}) + /** * FormShowOnObject * @@ -2494,17 +2553,15 @@ export const zModelProviderListResponse = z.object({ }) /** - * Type + * PluginParameterAutoGenerateType */ -export const zCorePluginEntitiesParametersPluginParameterAutoGenerateType = z.enum([ - 'prompt_instruction', -]) +export const zPluginParameterAutoGenerateType = z.enum(['prompt_instruction']) /** * PluginParameterAutoGenerate */ export const zPluginParameterAutoGenerate = z.object({ - type: zCorePluginEntitiesParametersPluginParameterAutoGenerateType, + type: zPluginParameterAutoGenerateType, }) /** @@ -2748,35 +2805,35 @@ export const zPostWorkspacesCurrentEndpointsBody = zEndpointCreatePayload /** * Endpoint created successfully */ -export const zPostWorkspacesCurrentEndpointsResponse = zEndpointCreateResponse +export const zPostWorkspacesCurrentEndpointsResponse = zSuccessResponse export const zPostWorkspacesCurrentEndpointsCreateBody = zEndpointCreatePayload /** * Endpoint created successfully */ -export const zPostWorkspacesCurrentEndpointsCreateResponse = zEndpointCreateResponse +export const zPostWorkspacesCurrentEndpointsCreateResponse = zSuccessResponse export const zPostWorkspacesCurrentEndpointsDeleteBody = zEndpointIdPayload /** * Endpoint deleted successfully */ -export const zPostWorkspacesCurrentEndpointsDeleteResponse = zEndpointDeleteResponse +export const zPostWorkspacesCurrentEndpointsDeleteResponse = zSuccessResponse export const zPostWorkspacesCurrentEndpointsDisableBody = zEndpointIdPayload /** * Endpoint disabled successfully */ -export const zPostWorkspacesCurrentEndpointsDisableResponse = zEndpointDisableResponse +export const zPostWorkspacesCurrentEndpointsDisableResponse = zSuccessResponse export const zPostWorkspacesCurrentEndpointsEnableBody = zEndpointIdPayload /** * Endpoint enabled successfully */ -export const zPostWorkspacesCurrentEndpointsEnableResponse = zEndpointEnableResponse +export const zPostWorkspacesCurrentEndpointsEnableResponse = zSuccessResponse export const zGetWorkspacesCurrentEndpointsListQuery = z.object({ page: z.int().gte(1), @@ -2797,14 +2854,14 @@ export const zGetWorkspacesCurrentEndpointsListPluginQuery = z.object({ /** * Success */ -export const zGetWorkspacesCurrentEndpointsListPluginResponse = zPluginEndpointListResponse +export const zGetWorkspacesCurrentEndpointsListPluginResponse = zEndpointListResponse export const zPostWorkspacesCurrentEndpointsUpdateBody = zLegacyEndpointUpdatePayload /** * Endpoint updated successfully */ -export const zPostWorkspacesCurrentEndpointsUpdateResponse = zEndpointUpdateResponse +export const zPostWorkspacesCurrentEndpointsUpdateResponse = zSuccessResponse export const zDeleteWorkspacesCurrentEndpointsByIdPath = z.object({ id: z.string(), @@ -2813,7 +2870,7 @@ export const zDeleteWorkspacesCurrentEndpointsByIdPath = z.object({ /** * Endpoint deleted successfully */ -export const zDeleteWorkspacesCurrentEndpointsByIdResponse = zEndpointDeleteResponse +export const zDeleteWorkspacesCurrentEndpointsByIdResponse = zSuccessResponse export const zPatchWorkspacesCurrentEndpointsByIdBody = zEndpointUpdatePayload @@ -2824,7 +2881,7 @@ export const zPatchWorkspacesCurrentEndpointsByIdPath = z.object({ /** * Endpoint updated successfully */ -export const zPatchWorkspacesCurrentEndpointsByIdResponse = zEndpointUpdateResponse +export const zPatchWorkspacesCurrentEndpointsByIdResponse = zSuccessResponse /** * Success @@ -2861,7 +2918,7 @@ export const zDeleteWorkspacesCurrentMembersByMemberIdPath = z.object({ /** * Success */ -export const zDeleteWorkspacesCurrentMembersByMemberIdResponse = zMemberActionTenantResponse +export const zDeleteWorkspacesCurrentMembersByMemberIdResponse = zMemberActionResponse export const zPostWorkspacesCurrentMembersByMemberIdOwnerTransferBody = zOwnerTransferPayload @@ -4442,7 +4499,11 @@ export const zPostWorkspacesCustomConfigBody = zWorkspaceCustomConfigPayload /** * Success */ -export const zPostWorkspacesCustomConfigResponse = zWorkspaceMutationResponse +export const zPostWorkspacesCustomConfigResponse = zWorkspaceTenantResultResponse + +export const zPostWorkspacesCustomConfigWebappLogoUploadBody = z.object({ + file: z.custom(), +}) /** * Logo uploaded @@ -4454,7 +4515,7 @@ export const zPostWorkspacesInfoBody = zWorkspaceInfoPayload /** * Success */ -export const zPostWorkspacesInfoResponse = zWorkspaceMutationResponse +export const zPostWorkspacesInfoResponse = zWorkspaceTenantResultResponse export const zPostWorkspacesSwitchBody = zSwitchWorkspacePayload diff --git a/packages/contracts/generated/api/openapi/types.gen.ts b/packages/contracts/generated/api/openapi/types.gen.ts index 622fb9c63f8..74217608500 100644 --- a/packages/contracts/generated/api/openapi/types.gen.ts +++ b/packages/contracts/generated/api/openapi/types.gen.ts @@ -369,10 +369,12 @@ export type PermittedExternalAppsListResponse = { export type PluginDependency = { current_identifier?: string | null - type: Type + type: PluginDependencyType value: Github | Marketplace | Package } +export type PluginDependencyType = 'github' | 'marketplace' | 'package' + export type RevokeResponse = { status: string } @@ -415,8 +417,6 @@ export type TaskStopResponse = { result: 'success' } -export type Type = 'github' | 'marketplace' | 'package' - export type UsageInfo = { completion_tokens?: number prompt_tokens?: number diff --git a/packages/contracts/generated/api/openapi/zod.gen.ts b/packages/contracts/generated/api/openapi/zod.gen.ts index 446f47817ef..3faf3395526 100644 --- a/packages/contracts/generated/api/openapi/zod.gen.ts +++ b/packages/contracts/generated/api/openapi/zod.gen.ts @@ -450,6 +450,27 @@ export const zPermittedExternalAppsListResponse = z.object({ total: z.int(), }) +/** + * PluginDependencyType + */ +export const zPluginDependencyType = z.enum(['github', 'marketplace', 'package']) + +/** + * PluginDependency + */ +export const zPluginDependency = z.object({ + current_identifier: z.string().nullish(), + type: zPluginDependencyType, + value: z.union([zGithub, zMarketplace, zPackage]), +}) + +/** + * CheckDependenciesResult + */ +export const zCheckDependenciesResult = z.object({ + leaked_dependencies: z.array(zPluginDependency).optional(), +}) + /** * RevokeResponse */ @@ -567,27 +588,6 @@ export const zTaskStopResponse = z.object({ result: z.literal('success'), }) -/** - * Type - */ -export const zType = z.enum(['github', 'marketplace', 'package']) - -/** - * PluginDependency - */ -export const zPluginDependency = z.object({ - current_identifier: z.string().nullish(), - type: zType, - value: z.union([zGithub, zMarketplace, zPackage]), -}) - -/** - * CheckDependenciesResult - */ -export const zCheckDependenciesResult = z.object({ - leaked_dependencies: z.array(zPluginDependency).optional(), -}) - /** * UsageInfo */