mirror of
https://github.com/langgenius/dify.git
synced 2026-11-10 21:35:45 +08:00
refactor(api): consolidate deployment edition handling (#40142)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
7fffd51535
commit
c1e94f8ee7
250 files changed
+1217
-1378
No files matched your search
@@ -33,7 +33,6 @@ RUN uv sync --frozen --no-dev --no-editable
|
||||
FROM base AS production
|
||||
|
||||
ENV FLASK_APP=app.py
|
||||
ENV EDITION=SELF_HOSTED
|
||||
ENV DEPLOY_ENV=PRODUCTION
|
||||
ENV CONSOLE_API_URL=http://127.0.0.1:5001
|
||||
ENV CONSOLE_WEB_URL=http://127.0.0.1:3000
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ from contexts.wrapper import RecyclableContextVar
|
||||
from controllers.console.error import UnauthorizedAndForceLogout
|
||||
from core.logging.context import init_request_context
|
||||
from dify_app import DifyApp
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_socketio import sio
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
from services.entities.feature_entities import LicenseStatus
|
||||
@@ -112,7 +113,7 @@ def create_flask_app_with_configs() -> DifyApp:
|
||||
init_request_context()
|
||||
RecyclableContextVar.increment_thread_recycles()
|
||||
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
surface = _match_license_gated_surface(request.path)
|
||||
if surface is not None:
|
||||
try:
|
||||
|
||||
@@ -10,6 +10,8 @@ import click
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from services.clear_free_plan_tenant_expired_logs import ClearFreePlanTenantExpiredLogs
|
||||
@@ -126,14 +128,12 @@ def _get_archive_candidate_tenant_ids_by_prefix(
|
||||
|
||||
|
||||
def _filter_paid_workflow_archive_tenant_ids(tenant_ids: list[str]) -> tuple[list[str], list[str]]:
|
||||
from configs import dify_config
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from services.billing_service import BillingService
|
||||
|
||||
tenant_ids = sorted(set(tenant_ids))
|
||||
if not tenant_ids:
|
||||
return [], []
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return tenant_ids, []
|
||||
|
||||
plans = BillingService.get_plan_bulk_with_cache(tenant_ids)
|
||||
@@ -1462,7 +1462,7 @@ def cleanup_orphaned_draft_variables(
|
||||
"--graceful-period",
|
||||
default=21,
|
||||
show_default=True,
|
||||
help="Graceful period in days after subscription expiration, will be ignored when billing is disabled.",
|
||||
help="Graceful period in days after subscription expiration; ignored outside the Cloud edition.",
|
||||
)
|
||||
@click.option("--dry-run", is_flag=True, default=False, help="Show messages logs would be cleaned without deleting")
|
||||
def clean_expired_messages(
|
||||
@@ -1514,8 +1514,8 @@ def clean_expired_messages(
|
||||
if from_days_ago <= before_days:
|
||||
raise click.UsageError("--from-days-ago must be greater than --before-days.")
|
||||
|
||||
# Create policy based on billing configuration
|
||||
# NOTE: graceful_period will be ignored when billing is disabled.
|
||||
# Create the policy for the configured deployment edition.
|
||||
# NOTE: graceful_period is ignored outside the Cloud edition.
|
||||
policy = create_message_clean_policy(graceful_period_days=graceful_period)
|
||||
|
||||
if from_days_ago is not None and before_days is not None:
|
||||
|
||||
@@ -6,7 +6,7 @@ from sqlalchemy import delete, select, update
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from events.app_event import app_was_created
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
|
||||
@@ -5,7 +5,6 @@ from typing import Any, override
|
||||
from pydantic.fields import FieldInfo
|
||||
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, TomlConfigSettingsSource
|
||||
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from libs.file_utils import search_file_upwards
|
||||
|
||||
from .deploy import DeploymentConfig
|
||||
@@ -117,11 +116,3 @@ class DifyConfig(
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def DEPLOYMENT_EDITION(self) -> DeploymentEdition:
|
||||
if self.EDITION == "CLOUD":
|
||||
return DeploymentEdition.CLOUD
|
||||
if self.ENTERPRISE_ENABLED:
|
||||
return DeploymentEdition.ENTERPRISE
|
||||
return DeploymentEdition.COMMUNITY
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
from enums import DeploymentEdition
|
||||
|
||||
|
||||
class DeploymentConfig(BaseSettings):
|
||||
"""
|
||||
@@ -25,9 +25,9 @@ class DeploymentConfig(BaseSettings):
|
||||
default=False,
|
||||
)
|
||||
|
||||
EDITION: Literal["SELF_HOSTED", "CLOUD"] = Field(
|
||||
description="Deployment edition of the application (e.g., 'SELF_HOSTED', 'CLOUD')",
|
||||
default="SELF_HOSTED",
|
||||
DEPLOYMENT_EDITION: DeploymentEdition = Field(
|
||||
description="Product edition of the application.",
|
||||
default=DeploymentEdition.COMMUNITY,
|
||||
)
|
||||
|
||||
DEPLOY_ENV: str = Field(
|
||||
|
||||
@@ -8,12 +8,6 @@ class EnterpriseFeatureConfig(BaseSettings):
|
||||
**Before using, please contact business@dify.ai by email to inquire about licensing matters.**
|
||||
"""
|
||||
|
||||
ENTERPRISE_ENABLED: bool = Field(
|
||||
description="Enable or disable enterprise-level features."
|
||||
"Before using, please contact business@dify.ai by email to inquire about licensing matters.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
WEBAPP_PUBLIC_ACCESS_ENABLED: bool = Field(
|
||||
description="Whether admins are allowed to set a webapp's access mode to public (anyone with the link, "
|
||||
"no auth). Disable in security-sensitive on-prem deployments.",
|
||||
@@ -59,7 +53,7 @@ class EnterpriseTelemetryConfig(BaseSettings):
|
||||
"""
|
||||
|
||||
ENTERPRISE_TELEMETRY_ENABLED: bool = Field(
|
||||
description="Enable enterprise telemetry collection (also requires ENTERPRISE_ENABLED=true).",
|
||||
description="Enable enterprise telemetry collection for enterprise deployments.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -810,17 +810,6 @@ class ModelLoadBalanceConfig(BaseSettings):
|
||||
)
|
||||
|
||||
|
||||
class BillingConfig(BaseSettings):
|
||||
"""
|
||||
Configuration for platform billing features
|
||||
"""
|
||||
|
||||
BILLING_ENABLED: bool = Field(
|
||||
description="Enable or disable billing functionality",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
class UpdateConfig(BaseSettings):
|
||||
"""
|
||||
Configuration for application update checks
|
||||
@@ -1621,7 +1610,6 @@ class FeatureConfig(
|
||||
# place the configs in alphabet order
|
||||
AppExecutionConfig,
|
||||
AuthConfig, # Changed from OAuthConfig to AuthConfig
|
||||
BillingConfig,
|
||||
CodeExecutionSandboxConfig,
|
||||
CreatorsPlatformConfig,
|
||||
TriggerConfig,
|
||||
|
||||
@@ -126,12 +126,6 @@ class UsageCountResponse(ResponseModel):
|
||||
count: int
|
||||
|
||||
|
||||
class IndexInfoResponse(ResponseModel):
|
||||
welcome: str
|
||||
api_version: str
|
||||
server_version: str
|
||||
|
||||
|
||||
class AvatarUrlResponse(ResponseModel):
|
||||
avatar_url: str
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from controllers.console import console_ns
|
||||
from controllers.console.auth.error import InvitationAccountMismatchError
|
||||
from controllers.console.error import AccountInFreezeError, AlreadyActivateError
|
||||
from controllers.console.wraps import model_validate
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.helper import EmailStr, timezone
|
||||
@@ -161,7 +162,9 @@ class ActivateApi(Resource):
|
||||
if current_account.id != account.id:
|
||||
raise InvitationAccountMismatchError()
|
||||
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(account.email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(
|
||||
account.email
|
||||
):
|
||||
raise AccountInFreezeError()
|
||||
|
||||
tenant = invitation["tenant"]
|
||||
|
||||
@@ -15,6 +15,7 @@ from controllers.console.auth.error import (
|
||||
InvalidTokenError,
|
||||
PasswordMismatchError,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from libs.helper import EmailStr, extract_remote_ip
|
||||
@@ -98,7 +99,9 @@ class EmailRegisterSendEmailApi(Resource):
|
||||
if req_data.language is not None and req_data.language in languages:
|
||||
language = req_data.language
|
||||
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(normalized_email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(
|
||||
normalized_email
|
||||
):
|
||||
raise AccountInFreezeError()
|
||||
|
||||
account = AccountService.get_account_by_email_with_case_fallback(req_data.email, session=db.session())
|
||||
|
||||
@@ -43,6 +43,7 @@ from controllers.console.wraps import (
|
||||
setup_required,
|
||||
with_current_user,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import EmailStr, extract_remote_ip
|
||||
from libs.helper import timezone as validate_timezone_string
|
||||
@@ -121,7 +122,9 @@ class LoginApi(Resource):
|
||||
request_email = req_data.email
|
||||
normalized_email = request_email.lower()
|
||||
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(normalized_email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(
|
||||
normalized_email
|
||||
):
|
||||
_log_console_login_failure(email=normalized_email, reason=LoginFailureReason.ACCOUNT_IN_FREEZE)
|
||||
raise AccountInFreezeError()
|
||||
|
||||
@@ -160,7 +163,6 @@ class LoginApi(Resource):
|
||||
AccountService.add_login_error_rate_limit(normalized_email)
|
||||
_log_console_login_failure(email=normalized_email, reason=LoginFailureReason.INVALID_CREDENTIALS)
|
||||
raise AuthenticationFailedError() from exc
|
||||
# SELF_HOSTED only have one workspace
|
||||
tenants = TenantService.get_join_tenants(account, session=db.session())
|
||||
if len(tenants) == 0:
|
||||
if (
|
||||
|
||||
@@ -12,6 +12,7 @@ from configs import dify_config
|
||||
from constants.languages import languages
|
||||
from controllers.common.fields import RedirectResponse
|
||||
from controllers.common.schema import query_params_from_model, register_response_schema_model, register_schema_models
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.helper import extract_remote_ip
|
||||
@@ -301,7 +302,9 @@ def _generate_account(
|
||||
normalized_email = user_info.email.lower()
|
||||
oauth_new_user = True
|
||||
if not FeatureService.get_system_features().is_allow_register:
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(normalized_email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(
|
||||
normalized_email
|
||||
):
|
||||
raise AccountRegisterError(
|
||||
description=(
|
||||
"This email account has been deleted within the past "
|
||||
|
||||
@@ -15,7 +15,7 @@ from controllers.console.wraps import (
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
)
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from libs.login import login_required
|
||||
|
||||
@@ -9,7 +9,7 @@ from flask import Response, abort, request
|
||||
from configs import dify_config
|
||||
from controllers.console.wraps import account_initialization_required, enterprise_license_required, setup_required
|
||||
from core.logging.context import get_request_id, get_trace_id
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from machinery.context import RequestContext
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.fastopenapi import console_router
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from models.model import DifySetup
|
||||
from services.account_service import TenantService
|
||||
|
||||
@@ -73,7 +73,7 @@ def setup_system(payload: SetupRequestPayload) -> SetupResponse:
|
||||
"""Initialize system setup with admin account.
|
||||
|
||||
NOTE: This endpoint is unauthenticated by design for first-time bootstrap.
|
||||
Access is restricted by deployment mode (`SELF_HOSTED`), one-time setup guards,
|
||||
Access is restricted to self-hosted editions (`COMMUNITY` and `ENTERPRISE`), one-time setup guards,
|
||||
and init-password validation rather than user session authentication.
|
||||
"""
|
||||
try:
|
||||
|
||||
@@ -11,7 +11,6 @@ from controllers.common.schema import register_response_schema_models, register_
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
cloud_edition_billing_enabled,
|
||||
cloud_edition_billing_paid_plan_required,
|
||||
model_validate,
|
||||
only_edition_cloud,
|
||||
@@ -122,7 +121,6 @@ class WorkflowRunArchivesApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@cloud_edition_billing_paid_plan_required
|
||||
def get(self):
|
||||
tenant_id, _ = _current_owner_or_admin_ids()
|
||||
@@ -143,7 +141,6 @@ class WorkflowRunArchiveDownloadsApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@cloud_edition_billing_paid_plan_required
|
||||
@model_validate(WorkflowRunArchiveDownloadPayload)
|
||||
def post(self, req_data: WorkflowRunArchiveDownloadPayload):
|
||||
@@ -170,7 +167,6 @@ class WorkflowRunArchiveDownloadApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@cloud_edition_billing_paid_plan_required
|
||||
def get(self, download_id: str):
|
||||
tenant_id, _ = _current_owner_or_admin_ids()
|
||||
@@ -195,7 +191,6 @@ class WorkflowRunArchiveDownloadFileApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@cloud_edition_billing_paid_plan_required
|
||||
def get(self, download_id: str):
|
||||
tenant_id, _ = _current_owner_or_admin_ids()
|
||||
|
||||
@@ -43,7 +43,6 @@ from controllers.console.workspace.error import (
|
||||
)
|
||||
from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
cloud_edition_billing_enabled,
|
||||
enable_change_email,
|
||||
enterprise_license_required,
|
||||
model_validate,
|
||||
@@ -51,7 +50,7 @@ from controllers.console.wraps import (
|
||||
setup_required,
|
||||
with_current_user,
|
||||
)
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from fields.member_fields import AccountResponse
|
||||
@@ -541,7 +540,6 @@ class EducationVerifyApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@console_ns.response(HTTPStatus.OK, "Success", console_ns.models[EducationVerifyResponse.__name__])
|
||||
@with_current_user
|
||||
def get(self, account: Account):
|
||||
@@ -559,7 +557,6 @@ class EducationApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@with_current_user
|
||||
def post(self, account: Account):
|
||||
raise EducationDiscountTemporarilyPausedError()
|
||||
@@ -568,7 +565,6 @@ class EducationApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@console_ns.response(HTTPStatus.OK, "Success", console_ns.models[EducationStatusResponse.__name__])
|
||||
@with_current_user
|
||||
def get(self, account: Account):
|
||||
@@ -586,7 +582,6 @@ class EducationAutoCompleteApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@only_edition_cloud
|
||||
@cloud_edition_billing_enabled
|
||||
@console_ns.response(HTTPStatus.OK, "Success", console_ns.models[EducationAutocompleteResponse.__name__])
|
||||
def get(self):
|
||||
payload = request.args.to_dict(flat=True)
|
||||
|
||||
@@ -32,6 +32,7 @@ from controllers.console.wraps import (
|
||||
setup_required,
|
||||
with_current_user,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_application_services import application_services
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
@@ -179,7 +180,7 @@ def _check_member_invite_limits(tenant_id: str, new_member_count: int, new_accou
|
||||
|
||||
features = FeatureService.get_features(tenant_id=tenant_id, exclude_vector_space=True)
|
||||
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
workspace_members = features.workspace_members
|
||||
if workspace_members.enabled is True and not workspace_members.is_available(new_member_count):
|
||||
raise WorkspaceMembersLimitExceeded()
|
||||
@@ -189,7 +190,7 @@ def _check_member_invite_limits(tenant_id: str, new_member_count: int, new_accou
|
||||
raise SeatsLimitExceeded()
|
||||
return
|
||||
|
||||
if dify_config.BILLING_ENABLED and features.billing.enabled is True:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
members = features.members
|
||||
current_member_count = _count_current_members(tenant_id)
|
||||
if 0 < members.limit < current_member_count + new_member_count:
|
||||
@@ -265,7 +266,10 @@ class MemberInviteEmailApi(Resource):
|
||||
|
||||
tenant_id = inviter.current_tenant.id
|
||||
with redis_client.lock(f"workspace_member_invite:{tenant_id}", timeout=60):
|
||||
if dify_config.ENTERPRISE_ENABLED is True or dify_config.BILLING_ENABLED is True:
|
||||
if dify_config.DEPLOYMENT_EDITION in {
|
||||
DeploymentEdition.CLOUD,
|
||||
DeploymentEdition.ENTERPRISE,
|
||||
}:
|
||||
new_member_count, new_account_count = _count_new_member_invites(tenant_id, invitee_emails)
|
||||
_check_member_invite_limits(tenant_id, new_member_count, new_account_count)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from controllers.console import console_ns
|
||||
from controllers.console.wraps import RBACPermission, RBACResourceScope, model_validate, rbac_permission_required
|
||||
from core.db.session_factory import session_factory
|
||||
from core.rbac import RBACResourceWhitelistScope
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models import Account
|
||||
@@ -316,14 +317,16 @@ class RBACRolesApi(Resource):
|
||||
options = req_data.to_inner_options()
|
||||
if not dify_config.RBAC_ENABLED:
|
||||
result = _legacy_workspace_roles(
|
||||
options, include_owner=req_data.include_owner, billing_enabled=dify_config.BILLING_ENABLED
|
||||
options,
|
||||
include_owner=req_data.include_owner,
|
||||
billing_enabled=dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD,
|
||||
)
|
||||
else:
|
||||
result = svc.RBACService.Roles.list(
|
||||
tenant_id,
|
||||
account_id,
|
||||
include_owner=req_data.include_owner,
|
||||
biiling_enabled=dify_config.BILLING_ENABLED,
|
||||
biiling_enabled=dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD,
|
||||
options=options,
|
||||
)
|
||||
|
||||
@@ -351,7 +354,12 @@ class RBACRoleItemApi(Resource):
|
||||
def get(self, role_id):
|
||||
tenant_id, account_id = _current_ids()
|
||||
return _dump(
|
||||
svc.RBACService.Roles.get(tenant_id, account_id, role_id, billing_enabled=dify_config.BILLING_ENABLED)
|
||||
svc.RBACService.Roles.get(
|
||||
tenant_id,
|
||||
account_id,
|
||||
role_id,
|
||||
billing_enabled=dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD,
|
||||
)
|
||||
)
|
||||
|
||||
@login_required
|
||||
|
||||
@@ -61,6 +61,7 @@ from core.tools.entities.tool_entities import (
|
||||
ToolProviderType,
|
||||
WorkflowToolParameterConfiguration,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from fields.base import ResponseModel
|
||||
from libs.helper import alphanumeric, dump_response, uuid_value
|
||||
@@ -282,10 +283,10 @@ def _resolve_identity_mode(requested: IdentityMode | None, *, current: IdentityM
|
||||
can never imply forwarding that the runtime won't perform. This gates the
|
||||
API surface to match the backend gate in
|
||||
``MCPTool._forwarding_requested`` — both the API and the backend
|
||||
invocation must be gated on ``dify_config.ENTERPRISE_ENABLED``.
|
||||
invocation must be gated on the Enterprise deployment edition.
|
||||
"""
|
||||
mode = current if requested is None else requested
|
||||
if mode != IdentityMode.OFF and not dify_config.ENTERPRISE_ENABLED:
|
||||
if mode != IdentityMode.OFF and dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return IdentityMode.OFF
|
||||
return mode
|
||||
|
||||
@@ -1417,7 +1418,7 @@ class ToolProviderMCPApi(Resource):
|
||||
with sessionmaker(db.engine).begin() as session:
|
||||
service = MCPToolManageService(session=session)
|
||||
# Resolve "leave unchanged" (None) against the stored value, and gate
|
||||
# the result on ENTERPRISE_ENABLED — both are API-layer concerns, so
|
||||
# the result on the Enterprise edition — both are API-layer concerns, so
|
||||
# the service receives a concrete IdentityMode.
|
||||
existing = service.get_provider(provider_id=req_data.provider_id, tenant_id=current_tenant_id)
|
||||
identity_mode = _resolve_identity_mode(req_data.identity_mode, current=IdentityMode(existing.identity_mode))
|
||||
|
||||
@@ -19,8 +19,7 @@ from controllers.common.wraps import (
|
||||
)
|
||||
from controllers.console.auth.error import AuthenticationFailedError, EmailCodeError
|
||||
from controllers.console.workspace.error import AccountNotInitializedError
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from libs.encryption import FieldEncryption
|
||||
@@ -142,7 +141,7 @@ def only_edition_cloud[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
def only_edition_enterprise[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(view)
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs):
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
abort(404)
|
||||
|
||||
return view(*args, **kwargs)
|
||||
@@ -161,16 +160,6 @@ def only_edition_self_hosted[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
return decorated
|
||||
|
||||
|
||||
def cloud_edition_billing_enabled[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(view)
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs):
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
abort(403, "Billing feature is not enabled.")
|
||||
return view(*args, **kwargs)
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def cloud_edition_billing_paid_plan_required[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(view)
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs):
|
||||
@@ -192,7 +181,7 @@ def cloud_edition_billing_resource_check[**P, R](resource: str) -> Callable[[Cal
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs):
|
||||
_, current_tenant_id = current_account_with_tenant()
|
||||
if resource == "vector_space":
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return view(*args, **kwargs)
|
||||
|
||||
vector_space = FeatureService.get_vector_space(current_tenant_id)
|
||||
@@ -301,7 +290,7 @@ def cloud_utm_record[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs):
|
||||
with contextlib.suppress(Exception):
|
||||
utm_info = request.cookies.get("utm_info")
|
||||
if dify_config.BILLING_ENABLED and utm_info:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and utm_info:
|
||||
_, current_tenant_id = current_account_with_tenant()
|
||||
utm_info_dict: UtmInfo = json.loads(utm_info)
|
||||
OperationService.record_utm(current_tenant_id, utm_info_dict)
|
||||
|
||||
@@ -139,7 +139,6 @@ register_response_schema_models(
|
||||
register_enum_models(openapi_ns, OpenApiErrorCode)
|
||||
|
||||
from . import (
|
||||
_meta,
|
||||
account,
|
||||
app_dsl,
|
||||
app_run,
|
||||
@@ -157,7 +156,6 @@ from . import (
|
||||
# Request models are imported from _models.py and registered above.
|
||||
|
||||
__all__ = [
|
||||
"_meta",
|
||||
"account",
|
||||
"app_dsl",
|
||||
"app_run",
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
"""Meta endpoint: `GET /openapi/v1/_version` — no auth.
|
||||
|
||||
Returns the server's project version and edition so the difyctl CLI can probe
|
||||
compatibility without needing to be logged in. Mirrors the `_health` endpoint
|
||||
in `index.py`.
|
||||
"""
|
||||
|
||||
from flask_restx import Resource
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.openapi import openapi_ns
|
||||
from controllers.openapi._contract import returns
|
||||
from controllers.openapi._models import ServerVersionResponse
|
||||
|
||||
|
||||
@openapi_ns.route("/_version")
|
||||
class VersionApi(Resource):
|
||||
@returns(200, ServerVersionResponse, description="Server version")
|
||||
def get(self):
|
||||
edition = dify_config.EDITION if dify_config.EDITION in ("SELF_HOSTED", "CLOUD") else "SELF_HOSTED"
|
||||
return ServerVersionResponse(
|
||||
version=dify_config.project.version,
|
||||
edition=edition,
|
||||
)
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Final, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
from enums import DeploymentEdition
|
||||
from libs.helper import EmailStr, UUIDStr, UUIDStrOrEmpty, uuid_value
|
||||
from models.model import AppMode
|
||||
|
||||
@@ -258,7 +259,7 @@ class ServerVersionResponse(BaseModel):
|
||||
"""Meta endpoint payload for `GET /openapi/v1/_version` — no auth required."""
|
||||
|
||||
version: str
|
||||
edition: Literal["SELF_HOSTED", "CLOUD"]
|
||||
edition: DeploymentEdition
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
|
||||
@@ -23,7 +23,8 @@ from controllers.openapi._models import (
|
||||
)
|
||||
from controllers.openapi.apps import build_app_describe_response
|
||||
from controllers.openapi.auth.composition import auth_router
|
||||
from controllers.openapi.auth.data import AuthData, Edition
|
||||
from controllers.openapi.auth.data import AuthData
|
||||
from enums import DeploymentEdition
|
||||
from libs.oauth_bearer import Scope, TokenType
|
||||
from models import App
|
||||
from models.enums import AppStatus
|
||||
@@ -37,7 +38,7 @@ class PermittedExternalAppsListApi(Resource):
|
||||
@auth_router.guard(
|
||||
scope=Scope.APPS_READ_PERMITTED_EXTERNAL,
|
||||
allowed_token_types=frozenset({TokenType.OAUTH_EXTERNAL_SSO}),
|
||||
edition=frozenset({Edition.EE}),
|
||||
edition=frozenset({DeploymentEdition.ENTERPRISE}),
|
||||
)
|
||||
@returns(200, PermittedExternalAppsListResponse, description="Permitted external apps list")
|
||||
@accepts(query=PermittedExternalAppsListQuery)
|
||||
@@ -94,7 +95,7 @@ class PermittedExternalAppDescribeApi(Resource):
|
||||
@auth_router.guard(
|
||||
scope=Scope.APPS_READ_PERMITTED_EXTERNAL,
|
||||
allowed_token_types=frozenset({TokenType.OAUTH_EXTERNAL_SSO}),
|
||||
edition=frozenset({Edition.EE}),
|
||||
edition=frozenset({DeploymentEdition.ENTERPRISE}),
|
||||
)
|
||||
@returns(200, AppDescribeResponse, description="Permitted external app description")
|
||||
@accepts(query=AppDescribeQuery)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from controllers.openapi.auth.conditions import (
|
||||
EDITION_EE,
|
||||
EDITION_ENTERPRISE,
|
||||
HAS_ALLOWED_ROLES,
|
||||
HAS_RBAC,
|
||||
LOADED_APP_IS_PRIVATE,
|
||||
@@ -11,7 +11,6 @@ from controllers.openapi.auth.conditions import (
|
||||
WORKSPACE_MEMBERSHIP_REQUIRED,
|
||||
WORKSPACE_SCOPED,
|
||||
)
|
||||
from controllers.openapi.auth.data import Edition
|
||||
from controllers.openapi.auth.flow import When
|
||||
from controllers.openapi.auth.pipeline import AuthPipeline, PipelineRoute, PipelineRouter
|
||||
from controllers.openapi.auth.prepare import (
|
||||
@@ -33,6 +32,7 @@ from controllers.openapi.auth.verify import (
|
||||
check_workspace_mismatch,
|
||||
check_workspace_role,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
from libs.oauth_bearer import TokenType
|
||||
|
||||
account_pipeline = AuthPipeline(
|
||||
@@ -42,7 +42,7 @@ account_pipeline = AuthPipeline(
|
||||
When(WORKSPACE_MEMBERSHIP_REQUIRED, then=load_tenant_from_request),
|
||||
load_account,
|
||||
When(WORKSPACE_SCOPED, then=load_workspace_role),
|
||||
When(PATH_HAS_APP_ID & EDITION_EE, then=load_app_access_mode),
|
||||
When(PATH_HAS_APP_ID & EDITION_ENTERPRISE, then=load_app_access_mode),
|
||||
],
|
||||
auth=[
|
||||
When(PATH_HAS_APP_ID, then=check_app_api_enabled),
|
||||
@@ -51,8 +51,8 @@ account_pipeline = AuthPipeline(
|
||||
When(PATH_HAS_APP_ID, then=check_workspace_mismatch),
|
||||
When(HAS_ALLOWED_ROLES, then=check_workspace_role),
|
||||
When(HAS_RBAC, then=check_rbac_permission),
|
||||
When(PATH_HAS_APP_ID & EDITION_EE & WEBAPP_AUTH_ENABLED & WEBAPP_RUN_SCOPED, then=check_acl),
|
||||
When(EDITION_EE & LOADED_APP_IS_PRIVATE & WEBAPP_RUN_SCOPED, then=check_private_app_permission),
|
||||
When(PATH_HAS_APP_ID & EDITION_ENTERPRISE & WEBAPP_AUTH_ENABLED & WEBAPP_RUN_SCOPED, then=check_acl),
|
||||
When(EDITION_ENTERPRISE & LOADED_APP_IS_PRIVATE & WEBAPP_RUN_SCOPED, then=check_private_app_permission),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -74,6 +74,9 @@ external_sso_pipeline = AuthPipeline(
|
||||
auth_router = PipelineRouter(
|
||||
{
|
||||
TokenType.OAUTH_ACCOUNT: PipelineRoute(account_pipeline),
|
||||
TokenType.OAUTH_EXTERNAL_SSO: PipelineRoute(external_sso_pipeline, required_edition=frozenset({Edition.EE})),
|
||||
TokenType.OAUTH_EXTERNAL_SSO: PipelineRoute(
|
||||
external_sso_pipeline,
|
||||
required_edition=frozenset({DeploymentEdition.ENTERPRISE}),
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -2,7 +2,9 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from controllers.openapi.auth.data import AuthData, Edition, RequestContext, current_edition
|
||||
from configs import dify_config
|
||||
from controllers.openapi.auth.data import AuthData, RequestContext
|
||||
from enums import DeploymentEdition
|
||||
from libs.oauth_bearer import Scope, TokenType
|
||||
from services.enterprise.enterprise_service import WebAppAccessMode
|
||||
from services.feature_service import FeatureService
|
||||
@@ -44,9 +46,9 @@ TOKEN_IS_OAUTH_EXTERNAL_SSO = request_cond(lambda ctx: ctx.token_type == TokenTy
|
||||
|
||||
PATH_HAS_APP_ID = request_cond(lambda ctx: "app_id" in ctx.path_params)
|
||||
|
||||
EDITION_CE = config_cond(lambda: current_edition() == Edition.CE)
|
||||
EDITION_EE = config_cond(lambda: current_edition() == Edition.EE)
|
||||
EDITION_SAAS = config_cond(lambda: current_edition() == Edition.SAAS)
|
||||
EDITION_COMMUNITY = config_cond(lambda: dify_config.DEPLOYMENT_EDITION == DeploymentEdition.COMMUNITY)
|
||||
EDITION_ENTERPRISE = config_cond(lambda: dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE)
|
||||
EDITION_CLOUD = config_cond(lambda: dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD)
|
||||
|
||||
WEBAPP_AUTH_ENABLED = config_cond(lambda: FeatureService.get_system_features().webapp_auth.enabled)
|
||||
|
||||
|
||||
@@ -6,34 +6,18 @@ from enum import StrEnum
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from werkzeug.exceptions import InternalServerError
|
||||
|
||||
from configs import dify_config
|
||||
from core.rbac import RBACPermission, RBACResourceScope
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from libs.oauth_bearer import Scope, TokenType
|
||||
from models.account import Account, Tenant, TenantAccountRole
|
||||
from models.model import App, EndUser
|
||||
from services.enterprise.enterprise_service import WebAppAccessMode
|
||||
|
||||
|
||||
class Edition(StrEnum):
|
||||
CE = "ce"
|
||||
EE = "ee"
|
||||
SAAS = "saas"
|
||||
|
||||
|
||||
class CallerKind(StrEnum):
|
||||
ACCOUNT = "account"
|
||||
END_USER = "end_user"
|
||||
|
||||
|
||||
def current_edition() -> Edition:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
return Edition.SAAS
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
return Edition.EE
|
||||
return Edition.CE
|
||||
|
||||
|
||||
class ExternalIdentity(BaseModel):
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
|
||||
@@ -16,16 +16,16 @@ from flask import current_app, request
|
||||
from flask_login import user_logged_in
|
||||
from werkzeug.exceptions import Forbidden, NotFound, Unauthorized
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.openapi._audit import emit_wrong_surface
|
||||
from controllers.openapi.auth.data import (
|
||||
AuthData,
|
||||
Edition,
|
||||
ExternalIdentity,
|
||||
RBACRequirement,
|
||||
RequestContext,
|
||||
current_edition,
|
||||
)
|
||||
from controllers.openapi.auth.flow import When
|
||||
from enums import DeploymentEdition
|
||||
from libs.oauth_bearer import (
|
||||
AuthContext,
|
||||
Scope,
|
||||
@@ -112,7 +112,7 @@ class AuthPipeline:
|
||||
@dataclass(frozen=True)
|
||||
class PipelineRoute:
|
||||
pipeline: AuthPipeline
|
||||
required_edition: frozenset[Edition] | None = None
|
||||
required_edition: frozenset[DeploymentEdition] | None = None
|
||||
|
||||
|
||||
class PipelineRouter:
|
||||
@@ -131,7 +131,7 @@ class PipelineRouter:
|
||||
*,
|
||||
scope: Scope | None = None,
|
||||
allowed_token_types: frozenset[TokenType] | None = None,
|
||||
edition: frozenset[Edition] | None = None,
|
||||
edition: frozenset[DeploymentEdition] | None = None,
|
||||
workspace_membership: bool = False,
|
||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||
rbac: RBACRequirement | None = None,
|
||||
@@ -150,7 +150,7 @@ class PipelineRouter:
|
||||
*,
|
||||
scope: Scope | None = None,
|
||||
allowed_token_types: frozenset[TokenType] | None = None,
|
||||
edition: frozenset[Edition] | None = None,
|
||||
edition: frozenset[DeploymentEdition] | None = None,
|
||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||
rbac: RBACRequirement | None = None,
|
||||
) -> Callable:
|
||||
@@ -168,7 +168,7 @@ class PipelineRouter:
|
||||
*,
|
||||
scope: Scope | None,
|
||||
allowed_token_types: frozenset[TokenType] | None,
|
||||
edition: frozenset[Edition] | None,
|
||||
edition: frozenset[DeploymentEdition] | None,
|
||||
workspace_membership: bool,
|
||||
allowed_roles: frozenset[TenantAccountRole] | None,
|
||||
rbac: RBACRequirement | None,
|
||||
@@ -200,17 +200,17 @@ class PipelineRouter:
|
||||
*,
|
||||
scope: Scope | None,
|
||||
allowed_token_types: frozenset[TokenType] | None,
|
||||
edition: frozenset[Edition] | None,
|
||||
edition: frozenset[DeploymentEdition] | None,
|
||||
workspace_membership: bool = False,
|
||||
allowed_roles: frozenset[TenantAccountRole] | None = None,
|
||||
rbac: RBACRequirement | None = None,
|
||||
) -> Any:
|
||||
# 404 not 403 — this edition doesn't expose the feature at all
|
||||
if edition is not None and current_edition() not in edition:
|
||||
if edition is not None and dify_config.DEPLOYMENT_EDITION not in edition:
|
||||
raise NotFound()
|
||||
|
||||
license_checked = False
|
||||
if edition is not None and Edition.EE in edition:
|
||||
if edition is not None and DeploymentEdition.ENTERPRISE in edition:
|
||||
_check_license()
|
||||
license_checked = True
|
||||
|
||||
@@ -234,9 +234,9 @@ class PipelineRouter:
|
||||
raise Forbidden("unsupported_token_type")
|
||||
|
||||
if route.required_edition is not None:
|
||||
if current_edition() not in route.required_edition:
|
||||
if dify_config.DEPLOYMENT_EDITION not in route.required_edition:
|
||||
raise Forbidden("external_sso_requires_ee")
|
||||
if not license_checked and Edition.EE in route.required_edition:
|
||||
if not license_checked and DeploymentEdition.ENTERPRISE in route.required_edition:
|
||||
_check_license()
|
||||
|
||||
return route.pipeline._run(
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"""Unauthenticated health and version probes for the OpenAPI surface."""
|
||||
|
||||
from flask_restx import Resource
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.openapi import openapi_ns
|
||||
from controllers.openapi._contract import returns
|
||||
from controllers.openapi._models import HealthResponse
|
||||
from controllers.openapi._models import HealthResponse, ServerVersionResponse
|
||||
|
||||
|
||||
@openapi_ns.route("/_health")
|
||||
@@ -10,3 +13,13 @@ class HealthApi(Resource):
|
||||
@returns(200, HealthResponse, description="Health check")
|
||||
def get(self):
|
||||
return HealthResponse(ok=True)
|
||||
|
||||
|
||||
@openapi_ns.route("/_version")
|
||||
class VersionApi(Resource):
|
||||
@returns(200, ServerVersionResponse, description="Server version")
|
||||
def get(self):
|
||||
return ServerVersionResponse(
|
||||
version=dify_config.project.version,
|
||||
edition=dify_config.DEPLOYMENT_EDITION,
|
||||
)
|
||||
@@ -42,7 +42,7 @@ from core.errors.error import (
|
||||
QuotaExceededError,
|
||||
)
|
||||
from core.helper.trace_id_helper import get_external_trace_id, get_trace_session_id, omit_trace_session_id_from_payload
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from graphon.model_runtime.errors.invoke import InvokeError
|
||||
from libs import helper
|
||||
from libs.helper import UUIDStrOrEmpty
|
||||
@@ -376,7 +376,11 @@ class ChatApi(Resource):
|
||||
|
||||
payload = ChatRequestPayload.model_validate(omit_trace_session_id_from_payload(service_api_ns.payload) or {})
|
||||
|
||||
if app_mode == AppMode.ADVANCED_CHAT and payload.workflow_id and dify_config.BILLING_ENABLED:
|
||||
if (
|
||||
app_mode == AppMode.ADVANCED_CHAT
|
||||
and payload.workflow_id
|
||||
and dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD
|
||||
):
|
||||
billing_info = BillingService.get_info(app_model.tenant_id, exclude_vector_space=True)
|
||||
if billing_info["enabled"] and billing_info["subscription"]["plan"] == CloudPlan.SANDBOX:
|
||||
raise WorkflowVersionExecutionNotAllowedError()
|
||||
|
||||
@@ -45,7 +45,7 @@ from core.errors.error import (
|
||||
QuotaExceededError,
|
||||
)
|
||||
from core.helper.trace_id_helper import get_external_trace_id, get_trace_session_id, omit_trace_session_id_from_payload
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from fields.base import ResponseModel
|
||||
@@ -451,7 +451,7 @@ class WorkflowRunByIdApi(Resource):
|
||||
if app_mode != AppMode.WORKFLOW:
|
||||
raise NotWorkflowAppError()
|
||||
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
billing_info = BillingService.get_info(app_model.tenant_id, exclude_vector_space=True)
|
||||
if billing_info["enabled"] and billing_info["subscription"]["plan"] == CloudPlan.SANDBOX:
|
||||
raise WorkflowVersionExecutionNotAllowedError()
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
from flask_restx import Resource
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.common.fields import IndexInfoResponse
|
||||
from controllers.common.schema import register_response_schema_models
|
||||
from controllers.service_api import service_api_ns
|
||||
from fields.base import ResponseModel
|
||||
|
||||
|
||||
class IndexInfoResponse(ResponseModel):
|
||||
welcome: str
|
||||
api_version: str
|
||||
server_version: str
|
||||
|
||||
|
||||
register_response_schema_models(service_api_ns, IndexInfoResponse)
|
||||
|
||||
@@ -12,8 +19,8 @@ register_response_schema_models(service_api_ns, IndexInfoResponse)
|
||||
class IndexApi(Resource):
|
||||
@service_api_ns.response(200, "Success", service_api_ns.models[IndexInfoResponse.__name__])
|
||||
def get(self) -> dict[str, str]:
|
||||
return {
|
||||
"welcome": "Dify OpenAPI",
|
||||
"api_version": "v1",
|
||||
"server_version": dify_config.project.version,
|
||||
}
|
||||
return IndexInfoResponse(
|
||||
welcome="Dify OpenAPI",
|
||||
api_version="v1",
|
||||
server_version=dify_config.project.version,
|
||||
).model_dump(mode="json")
|
||||
@@ -22,7 +22,7 @@ from controllers.service_api.schema import (
|
||||
USER_QUERY_PARAM,
|
||||
USER_REQUIRED_ATTR,
|
||||
)
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from libs.login import current_user
|
||||
@@ -186,7 +186,7 @@ def cloud_edition_billing_resource_check[**P, R](
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs):
|
||||
api_token = validate_and_get_api_token(api_token_type)
|
||||
if resource == "vector_space":
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return view(*args, **kwargs)
|
||||
|
||||
vector_space = FeatureService.get_vector_space(api_token.tenant_id)
|
||||
|
||||
@@ -7,7 +7,7 @@ from werkzeug.exceptions import NotFound, RequestEntityTooLarge
|
||||
from controllers.trigger import bp
|
||||
from core.trigger.debug.event_bus import TriggerDebugEventBus
|
||||
from core.trigger.debug.events import WebhookDebugEvent, build_webhook_pool_key
|
||||
from enums.quota_type import QuotaType
|
||||
from enums import QuotaType
|
||||
from services.errors.app import QuotaExceededError
|
||||
from services.trigger.webhook_service import RawWebhookDataDict, WebhookService
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ from flask_restx import Resource
|
||||
|
||||
from controllers.common.schema import register_response_schema_models
|
||||
from controllers.web import web_ns
|
||||
from extensions.ext_application_services import application_services
|
||||
from libs.helper import dump_response
|
||||
from services.entities.feature_entities import SystemFeatureModel
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
register_response_schema_models(web_ns, SystemFeatureModel)
|
||||
|
||||
@@ -30,4 +30,7 @@ class SystemFeatureApi(Resource):
|
||||
|
||||
Authentication configuration must be available before the authentication flow can be selected.
|
||||
"""
|
||||
return dump_response(SystemFeatureModel, FeatureService.get_system_features())
|
||||
return dump_response(
|
||||
SystemFeatureModel,
|
||||
application_services().feature_queries.get_system_features(),
|
||||
)
|
||||
@@ -30,6 +30,7 @@ from controllers.console.wraps import (
|
||||
)
|
||||
from controllers.web import web_ns
|
||||
from controllers.web.wraps import decode_jwt_token
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import EmailStr, extract_remote_ip
|
||||
from libs.passport import PassportService
|
||||
@@ -146,8 +147,9 @@ class LoginStatusApi(Resource):
|
||||
if not app_code:
|
||||
return LoginStatusResponse(logged_in=bool(token), app_logged_in=False).model_dump(mode="json")
|
||||
app_id = AppService.get_app_id_by_code(app_code, session=db.session())
|
||||
is_public = not dify_config.ENTERPRISE_ENABLED or not WebAppAuthService.is_app_require_permission_check(
|
||||
app_id=app_id, session=db.session()
|
||||
is_public = (
|
||||
dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE
|
||||
or not WebAppAuthService.is_app_require_permission_check(app_id=app_id, session=db.session())
|
||||
)
|
||||
user_logged_in = False
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from configs import dify_config
|
||||
from controllers.common.schema import register_response_schema_models
|
||||
from controllers.web import web_ns
|
||||
from controllers.web.wraps import WebApiResource
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.storage.storage_type import StorageType
|
||||
from fields.base import ResponseModel
|
||||
|
||||
@@ -6,7 +6,7 @@ from pydantic import BaseModel
|
||||
from configs import dify_config
|
||||
from core.entities import DEFAULT_PLUGIN_ID
|
||||
from core.entities.provider_entities import ProviderQuotaType, QuotaUnit, RestrictModel
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from graphon.model_runtime.entities.model_entities import ModelType
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from core.rag.splitter.fixed_text_splitter import (
|
||||
)
|
||||
from core.rag.splitter.text_splitter import TextSplitter
|
||||
from core.tools.utils.web_reader_tool import get_image_upload_file_ids
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_redis import redis_client
|
||||
from extensions.ext_storage import storage
|
||||
from graphon.model_runtime.entities.model_entities import ModelType
|
||||
@@ -330,7 +331,7 @@ class IndexingRunner:
|
||||
Estimate the indexing for the document.
|
||||
"""
|
||||
# check document limit
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
count = len(extract_settings)
|
||||
batch_upload_limit = dify_config.BATCH_UPLOAD_LIMIT
|
||||
if count > batch_upload_limit:
|
||||
|
||||
@@ -58,6 +58,7 @@ from core.plugin.impl.debugging import PluginDebuggingClient
|
||||
from core.plugin.impl.endpoint import PluginEndpointClient
|
||||
from core.plugin.impl.model import PluginModelClient
|
||||
from core.plugin.impl.plugin import PluginInstaller
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from models.provider import Provider, ProviderCredential, TenantPreferredModelProvider
|
||||
@@ -1256,7 +1257,7 @@ class PluginService:
|
||||
PluginService.invalidate_plugin_model_providers_cache(tenant_id)
|
||||
return result
|
||||
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
PluginManagerService.try_pre_uninstall_plugin(
|
||||
PreUninstallPluginRequest(
|
||||
tenant_id=tenant_id,
|
||||
|
||||
@@ -36,7 +36,7 @@ from core.helper.model_provider_cache import ProviderCredentialsCache, ProviderC
|
||||
from core.helper.position_helper import is_filtered
|
||||
from core.plugin.entities.plugin import PluginInstallationSource
|
||||
from core.plugin.entities.plugin_daemon import PluginModelProviderDeclaration
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from extensions import ext_hosting_provider
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
|
||||
@@ -25,6 +25,7 @@ from core.tools.__base.tool import Tool
|
||||
from core.tools.__base.tool_runtime import ToolRuntime
|
||||
from core.tools.entities.tool_entities import ToolEntity, ToolInvokeMessage, ToolProviderType
|
||||
from core.tools.errors import ToolInvokeError
|
||||
from enums import DeploymentEdition
|
||||
from graphon.model_runtime.entities.llm_entities import LLMUsage, LLMUsageMetadata
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -272,7 +273,7 @@ class MCPTool(Tool):
|
||||
the deployment actually has the enterprise side that can mint tokens.
|
||||
Non-enterprise installs treat the DB value as a no-op — a stale row
|
||||
won't trigger a 5xx against a missing inner-API endpoint."""
|
||||
return self.identity_mode != IdentityMode.OFF and dify_config.ENTERPRISE_ENABLED
|
||||
return self.identity_mode != IdentityMode.OFF and dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE
|
||||
|
||||
def invoke_remote_mcp_tool(
|
||||
self,
|
||||
|
||||
@@ -31,13 +31,13 @@ if [[ "${MODE}" == "worker" ]]; then
|
||||
CONCURRENCY_OPTION="-c ${CELERY_WORKER_AMOUNT:-1}"
|
||||
fi
|
||||
|
||||
# Configure queues based on edition if not explicitly set
|
||||
# Configure queues based on product edition if not explicitly set
|
||||
if [[ -z "${CELERY_QUEUES}" ]]; then
|
||||
if [[ "${EDITION}" == "CLOUD" ]]; then
|
||||
if [[ "${DEPLOYMENT_EDITION:-COMMUNITY}" == "CLOUD" ]]; then
|
||||
# Cloud edition: separate queues for dataset and trigger tasks
|
||||
DEFAULT_QUEUES="api_token,dataset,dataset_summary,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,app_rbac,plugin,workflow_storage,conversation,workflow_professional,workflow_team,workflow_sandbox,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_publisher,trigger_refresh_executor,retention,workflow_based_app_execution"
|
||||
else
|
||||
# Community edition (SELF_HOSTED): dataset, pipeline and workflow have separate queues
|
||||
# Self-hosted editions: dataset, pipeline and workflow have separate queues
|
||||
DEFAULT_QUEUES="api_token,dataset,dataset_summary,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,app_rbac,plugin,workflow_storage,conversation,workflow,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_publisher,trigger_refresh_executor,retention,workflow_based_app_execution"
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -32,7 +32,7 @@ The Enterprise OTEL exporter is configured via environment variables.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `ENTERPRISE_ENABLED` | Master switch for all enterprise features. | `false` |
|
||||
| `DEPLOYMENT_EDITION` | Product edition; enterprise telemetry is only available in `ENTERPRISE`. | `COMMUNITY` |
|
||||
| `ENTERPRISE_TELEMETRY_ENABLED` | Master switch for enterprise telemetry. | `false` |
|
||||
| `ENTERPRISE_OTLP_ENDPOINT` | OTLP collector endpoint (e.g., `http://otel-collector:4318`). | - |
|
||||
| `ENTERPRISE_OTLP_HEADERS` | Custom headers for OTLP requests (e.g., `x-scope-orgid=tenant1`). | - |
|
||||
|
||||
@@ -42,12 +42,15 @@ from enterprise.telemetry.id_generator import (
|
||||
set_correlation_id,
|
||||
set_span_id_source,
|
||||
)
|
||||
from enums import DeploymentEdition
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_enterprise_telemetry_enabled() -> bool:
|
||||
return bool(dify_config.ENTERPRISE_ENABLED and dify_config.ENTERPRISE_TELEMETRY_ENABLED)
|
||||
return bool(
|
||||
dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE and dify_config.ENTERPRISE_TELEMETRY_ENABLED
|
||||
)
|
||||
|
||||
|
||||
def _parse_otlp_headers(raw: str) -> dict[str, str]:
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from enum import StrEnum, auto
|
||||
|
||||
|
||||
class CloudPlan(StrEnum):
|
||||
"""
|
||||
Enum representing user plan types in the cloud platform.
|
||||
|
||||
SANDBOX: Free/default plan with limited features
|
||||
PROFESSIONAL: Professional paid plan
|
||||
TEAM: Team collaboration paid plan
|
||||
"""
|
||||
|
||||
SANDBOX = auto()
|
||||
PROFESSIONAL = auto()
|
||||
TEAM = auto()
|
||||
|
||||
|
||||
class DeploymentEdition(StrEnum):
|
||||
"""Enum representing the deployment edition of the platform."""
|
||||
|
||||
COMMUNITY = "COMMUNITY"
|
||||
ENTERPRISE = "ENTERPRISE"
|
||||
CLOUD = "CLOUD"
|
||||
|
||||
|
||||
class HostedTrialProvider(StrEnum):
|
||||
"""Enum representing hosted model provider names for trial access."""
|
||||
|
||||
OPENAI = "langgenius/openai/openai"
|
||||
ANTHROPIC = "langgenius/anthropic/anthropic"
|
||||
GEMINI = "langgenius/gemini/google"
|
||||
X = "langgenius/x/x"
|
||||
DEEPSEEK = "langgenius/deepseek/deepseek"
|
||||
TONGYI = "langgenius/tongyi/tongyi"
|
||||
|
||||
@property
|
||||
def config_key(self) -> str:
|
||||
"""Return the config key used in dify_config (e.g., HOSTED_{config_key}_PAID_ENABLED)."""
|
||||
if self == HostedTrialProvider.X:
|
||||
return "XAI"
|
||||
return self.name
|
||||
|
||||
|
||||
class QuotaType(StrEnum):
|
||||
"""Supported quota types for tenant feature usage."""
|
||||
|
||||
TRIGGER = auto()
|
||||
WORKFLOW = auto()
|
||||
UNLIMITED = auto()
|
||||
|
||||
@property
|
||||
def billing_key(self) -> str:
|
||||
match self:
|
||||
case QuotaType.TRIGGER:
|
||||
return "trigger_event"
|
||||
case QuotaType.WORKFLOW:
|
||||
return "api_rate_limit"
|
||||
case _:
|
||||
raise ValueError(f"Invalid quota type: {self}")
|
||||
@@ -1,15 +0,0 @@
|
||||
from enum import StrEnum, auto
|
||||
|
||||
|
||||
class CloudPlan(StrEnum):
|
||||
"""
|
||||
Enum representing user plan types in the cloud platform.
|
||||
|
||||
SANDBOX: Free/default plan with limited features
|
||||
PROFESSIONAL: Professional paid plan
|
||||
TEAM: Team collaboration paid plan
|
||||
"""
|
||||
|
||||
SANDBOX = auto()
|
||||
PROFESSIONAL = auto()
|
||||
TEAM = auto()
|
||||
@@ -1,11 +0,0 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class DeploymentEdition(StrEnum):
|
||||
"""
|
||||
Enum representing the deployment edition of the platform.
|
||||
"""
|
||||
|
||||
COMMUNITY = "COMMUNITY"
|
||||
ENTERPRISE = "ENTERPRISE"
|
||||
CLOUD = "CLOUD"
|
||||
@@ -1,21 +0,0 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class HostedTrialProvider(StrEnum):
|
||||
"""
|
||||
Enum representing hosted model provider names for trial access.
|
||||
"""
|
||||
|
||||
OPENAI = "langgenius/openai/openai"
|
||||
ANTHROPIC = "langgenius/anthropic/anthropic"
|
||||
GEMINI = "langgenius/gemini/google"
|
||||
X = "langgenius/x/x"
|
||||
DEEPSEEK = "langgenius/deepseek/deepseek"
|
||||
TONGYI = "langgenius/tongyi/tongyi"
|
||||
|
||||
@property
|
||||
def config_key(self) -> str:
|
||||
"""Return the config key used in dify_config (e.g., HOSTED_{config_key}_PAID_ENABLED)."""
|
||||
if self == HostedTrialProvider.X:
|
||||
return "XAI"
|
||||
return self.name
|
||||
@@ -1,21 +0,0 @@
|
||||
from enum import StrEnum, auto
|
||||
|
||||
|
||||
class QuotaType(StrEnum):
|
||||
"""
|
||||
Supported quota types for tenant feature usage.
|
||||
"""
|
||||
|
||||
TRIGGER = auto()
|
||||
WORKFLOW = auto()
|
||||
UNLIMITED = auto()
|
||||
|
||||
@property
|
||||
def billing_key(self) -> str:
|
||||
match self:
|
||||
case QuotaType.TRIGGER:
|
||||
return "trigger_event"
|
||||
case QuotaType.WORKFLOW:
|
||||
return "api_rate_limit"
|
||||
case _:
|
||||
raise ValueError(f"Invalid quota type: {self}")
|
||||
@@ -1,4 +1,5 @@
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from events.tenant_event import tenant_was_created
|
||||
from services.enterprise.workspace_sync import WorkspaceSyncService
|
||||
|
||||
@@ -7,7 +8,7 @@ from services.enterprise.workspace_sync import WorkspaceSyncService
|
||||
def handle(sender, **kwargs):
|
||||
"""Queue credential sync when a tenant/workspace is created."""
|
||||
# Only queue sync tasks if plugin manager (enterprise feature) is enabled
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return
|
||||
|
||||
tenant = sender
|
||||
|
||||
@@ -10,7 +10,7 @@ from configs import dify_config
|
||||
from constants.dsl_version import CURRENT_APP_DSL_VERSION
|
||||
from core.db.session_factory import get_session_maker
|
||||
from core.schemas.schema_manager import SchemaManager
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_redis import RedisClientWrapper, redis_client
|
||||
from repositories.explore_banner_query_repository import ExploreBannerQueryRepository
|
||||
from repositories.installation_state_repository import InstallationStateRepository
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing_extensions import TypedDict
|
||||
|
||||
from configs import dify_config
|
||||
from dify_app import DifyApp
|
||||
from enums import DeploymentEdition
|
||||
from extensions.redis_names import normalize_redis_key_prefix
|
||||
from extensions.workflow_warm_shutdown import setup_workflow_warm_shutdown_handler
|
||||
|
||||
@@ -276,8 +277,7 @@ def init_app(app: DifyApp) -> Celery:
|
||||
}
|
||||
|
||||
if (
|
||||
dify_config.EDITION == "SELF_HOSTED"
|
||||
and not dify_config.ENTERPRISE_ENABLED
|
||||
dify_config.DEPLOYMENT_EDITION == DeploymentEdition.COMMUNITY
|
||||
and not dify_config.DISABLE_TELEMETRY
|
||||
and not dify_config.DO_NOT_TRACK
|
||||
and not dify_config.CI
|
||||
@@ -288,7 +288,7 @@ def init_app(app: DifyApp) -> Celery:
|
||||
"schedule": timedelta(minutes=dify_config.TELEMETRY_HEARTBEAT_INTERVAL_MINUTES),
|
||||
}
|
||||
|
||||
if dify_config.ENTERPRISE_ENABLED and dify_config.ENTERPRISE_TELEMETRY_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE and dify_config.ENTERPRISE_TELEMETRY_ENABLED:
|
||||
imports.append("tasks.enterprise_telemetry_task")
|
||||
celery_app.conf.update(beat_schedule=beat_schedule, imports=imports)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Initializes the EnterpriseExporter singleton during ``create_app()``
|
||||
(single-threaded), registers blinker event handlers, and hooks atexit
|
||||
for graceful shutdown.
|
||||
|
||||
Skipped entirely when either ``ENTERPRISE_ENABLED`` or ``ENTERPRISE_TELEMETRY_ENABLED``
|
||||
Skipped entirely outside the Enterprise edition or when ``ENTERPRISE_TELEMETRY_ENABLED``
|
||||
is false (``is_enabled()`` gate).
|
||||
"""
|
||||
|
||||
@@ -15,6 +15,7 @@ import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dify_app import DifyApp
|
||||
@@ -26,7 +27,9 @@ _exporter: EnterpriseExporter | None = None
|
||||
|
||||
|
||||
def is_enabled() -> bool:
|
||||
return bool(dify_config.ENTERPRISE_ENABLED and dify_config.ENTERPRISE_TELEMETRY_ENABLED)
|
||||
return bool(
|
||||
dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE and dify_config.ENTERPRISE_TELEMETRY_ENABLED
|
||||
)
|
||||
|
||||
|
||||
def init_app(app: DifyApp) -> None:
|
||||
|
||||
@@ -59,7 +59,7 @@ def init_app(app: DifyApp):
|
||||
SERVICE_NAME: dify_config.APPLICATION_NAME,
|
||||
SERVICE_VERSION: f"dify-{dify_config.project.version}-{dify_config.COMMIT_SHA}",
|
||||
PROCESS_PID: os.getpid(),
|
||||
DEPLOYMENT_ENVIRONMENT_NAME: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}",
|
||||
DEPLOYMENT_ENVIRONMENT_NAME: f"{dify_config.DEPLOY_ENV}-{dify_config.DEPLOYMENT_EDITION.value}",
|
||||
HOST_NAME: socket.gethostname(),
|
||||
HOST_ARCH: platform.machine(),
|
||||
"custom.deployment.git_commit": dify_config.COMMIT_SHA,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from configs import dify_config
|
||||
from dify_app import DifyApp
|
||||
from enums import DeploymentEdition
|
||||
|
||||
|
||||
def init_app(app: DifyApp):
|
||||
@@ -47,7 +48,7 @@ def init_app(app: DifyApp):
|
||||
before_send=before_send,
|
||||
)
|
||||
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
# Cloud only. `opentelemetry.context.detach()` catches its own failures and reports
|
||||
# them through `logger.exception`, so a double-detach surfaces as an error event
|
||||
# rather than as a raised exception. Under gevent this can fire about once per
|
||||
|
||||
@@ -3,7 +3,7 @@ Base parser interface and utilities for OpenTelemetry node parsers.
|
||||
|
||||
Content gating: ``should_include_content()`` controls whether content-bearing
|
||||
span attributes (inputs, outputs, prompts, completions, documents) are written.
|
||||
Gate is only active in EE (``ENTERPRISE_ENABLED=True``) when
|
||||
Gate is only active in the Enterprise edition when
|
||||
``ENTERPRISE_INCLUDE_CONTENT=False``; CE behaviour is unchanged.
|
||||
"""
|
||||
|
||||
@@ -15,6 +15,7 @@ from opentelemetry.trace.status import Status, StatusCode
|
||||
from pydantic import BaseModel
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from extensions.otel.semconv.gen_ai import ChainAttributes, GenAIAttributes
|
||||
from graphon.enums import BuiltinNodeTypes
|
||||
from graphon.file import File
|
||||
@@ -26,9 +27,9 @@ from graphon.variables import Segment
|
||||
def should_include_content() -> bool:
|
||||
"""Return True if content should be written to spans.
|
||||
|
||||
CE (ENTERPRISE_ENABLED=False): always True — no behaviour change.
|
||||
Community and Cloud editions: always True — no behaviour change.
|
||||
"""
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return True
|
||||
return dify_config.ENTERPRISE_INCLUDE_CONTENT
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from sqlalchemy.orm import Session
|
||||
from werkzeug.exceptions import Forbidden, ServiceUnavailable, Unauthorized
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from libs.rate_limit import enforce_bearer_rate_limit
|
||||
@@ -531,7 +532,7 @@ def require_workspace_member(ctx: AuthContext, tenant_id: str) -> None:
|
||||
No-op on EE (gateway RBAC owns tenant isolation) and for SSO subjects
|
||||
(no `tenant_account_joins` row by definition).
|
||||
"""
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
return
|
||||
if ctx.subject_type != SubjectType.ACCOUNT or ctx.account_id is None:
|
||||
return
|
||||
|
||||
@@ -12,6 +12,7 @@ import logging
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
@@ -32,8 +33,8 @@ def check_workspace_member_invite_permission(workspace_id: str) -> None:
|
||||
Raises:
|
||||
Forbidden: If either billing plan or workspace policy prohibits member invitations
|
||||
"""
|
||||
# Check enterprise workspace policy level (only if enterprise enabled)
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
# Check the enterprise workspace policy only in the Enterprise edition.
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
try:
|
||||
permission = EnterpriseService.WorkspacePermissionService.get_permission(workspace_id)
|
||||
if not permission.allow_member_invite:
|
||||
@@ -62,8 +63,8 @@ def check_workspace_owner_transfer_permission(workspace_id: str) -> None:
|
||||
if not features.is_allow_transfer_workspace:
|
||||
raise Forbidden("Your current plan does not allow workspace ownership transfer")
|
||||
|
||||
# Check enterprise workspace policy level (only if enterprise enabled)
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
# Check the enterprise workspace policy only in the Enterprise edition.
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
try:
|
||||
permission = EnterpriseService.WorkspacePermissionService.get_permission(workspace_id)
|
||||
if not permission.allow_owner_transfer:
|
||||
|
||||
@@ -15942,7 +15942,7 @@ Retrieval settings for Amazon Bedrock knowledge base queries.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| enabled | boolean | | Yes |
|
||||
| enabled | boolean | Deprecated. Use system features deployment_edition to determine the product edition. | Yes |
|
||||
| subscription | [SubscriptionModel](#subscriptionmodel) | | Yes |
|
||||
|
||||
#### BillingResponse
|
||||
@@ -24709,7 +24709,7 @@ FastOpenAPI proof of concept for Dify API
|
||||
**Initialize system setup with admin account.
|
||||
|
||||
NOTE: This endpoint is unauthenticated by design for first-time bootstrap.
|
||||
Access is restricted by deployment mode (`SELF_HOSTED`), one-time setup guards,
|
||||
Access is restricted to self-hosted editions (`COMMUNITY` and `ENTERPRISE`), one-time setup guards,
|
||||
and init-password validation rather than user session authentication.
|
||||
**
|
||||
|
||||
|
||||
@@ -648,6 +648,14 @@ mode is a closed enum of listable app types.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| leaked_dependencies | [ [PluginDependency](#plugindependency) ] | | No |
|
||||
|
||||
#### DeploymentEdition
|
||||
|
||||
Enum representing the deployment edition of the platform.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| DeploymentEdition | string | Enum representing the deployment edition of the platform. | |
|
||||
|
||||
#### DeviceCodeRequest
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -974,7 +982,7 @@ Meta endpoint payload for `GET /openapi/v1/_version` — no auth required.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| edition | string, <br>**Available values:** "CLOUD", "SELF_HOSTED" | *Enum:* `"CLOUD"`, `"SELF_HOSTED"` | Yes |
|
||||
| edition | [DeploymentEdition](#deploymentedition) | | Yes |
|
||||
| version | string | | Yes |
|
||||
|
||||
#### SessionListQuery
|
||||
|
||||
@@ -53,7 +53,7 @@ class TraceClient:
|
||||
attributes={
|
||||
service_attributes.SERVICE_NAME: service_name,
|
||||
service_attributes.SERVICE_VERSION: f"dify-{dify_config.project.version}-{dify_config.COMMIT_SHA}",
|
||||
DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}",
|
||||
DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.DEPLOYMENT_EDITION.value}",
|
||||
HOST_NAME: socket.gethostname(),
|
||||
ACS_ARMS_SERVICE_FEATURE: "genai_app",
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class TencentTraceClient:
|
||||
attributes={
|
||||
service_attributes.SERVICE_NAME: service_name,
|
||||
service_attributes.SERVICE_VERSION: f"dify-{dify_config.project.version}-{dify_config.COMMIT_SHA}",
|
||||
DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}",
|
||||
DEPLOYMENT_ENVIRONMENT: f"{dify_config.DEPLOY_ENV}-{dify_config.DEPLOYMENT_EDITION.value}",
|
||||
HOST_NAME: socket.gethostname(),
|
||||
"telemetry.sdk.language": "python",
|
||||
"telemetry.sdk.name": "opentelemetry",
|
||||
|
||||
@@ -16,6 +16,8 @@ from dify_trace_tencent.entities.tencent_trace_entity import SpanData
|
||||
from opentelemetry.sdk.trace import Event
|
||||
from opentelemetry.trace import SpanContext, Status, StatusCode, TraceFlags
|
||||
|
||||
from enums import DeploymentEdition
|
||||
|
||||
metric_reader_instances: list[DummyMetricReader] = []
|
||||
meter_provider_instances: list[DummyMeterProvider] = []
|
||||
|
||||
@@ -158,7 +160,7 @@ def patch_core_components(monkeypatch: pytest.MonkeyPatch) -> PatchedCoreCompone
|
||||
project=SimpleNamespace(version="test"),
|
||||
COMMIT_SHA="sha",
|
||||
DEPLOY_ENV="dev",
|
||||
EDITION="cloud",
|
||||
DEPLOYMENT_EDITION=DeploymentEdition.CLOUD,
|
||||
)
|
||||
monkeypatch.setattr(client_module, "dify_config", fake_config)
|
||||
|
||||
|
||||
@@ -19,15 +19,14 @@ def clean_messages():
|
||||
Clean expired messages based on clean policy.
|
||||
|
||||
This task uses MessagesCleanService to efficiently clean messages in batches.
|
||||
The behavior depends on BILLING_ENABLED configuration:
|
||||
- BILLING_ENABLED=True: only delete messages from sandbox tenants (with whitelist/grace period)
|
||||
- BILLING_ENABLED=False: delete all messages within the time range
|
||||
Cloud only deletes messages from sandbox tenants (with whitelist/grace period).
|
||||
Self-hosted editions delete all messages within the configured time range.
|
||||
"""
|
||||
click.echo(click.style("clean_messages: start clean messages.", fg="green"))
|
||||
start_at = time.perf_counter()
|
||||
|
||||
try:
|
||||
# Create policy based on billing configuration
|
||||
# Create policy based on deployment edition.
|
||||
policy = create_message_clean_policy(
|
||||
graceful_period_days=dify_config.SANDBOX_EXPIRED_RECORDS_CLEAN_GRACEFUL_PERIOD,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import app
|
||||
from configs import dify_config
|
||||
from core.db.session_factory import session_factory
|
||||
from core.rag.index_processor.index_processor_factory import IndexProcessorFactory
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from extensions.ext_redis import redis_client
|
||||
from libs.pagination import paginate_query
|
||||
from models.dataset import Dataset, DatasetAutoDisableLog, DatasetQuery, Document
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy import select
|
||||
import app
|
||||
from configs import dify_config
|
||||
from core.db.session_factory import session_factory
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from extensions.ext_mail import mail
|
||||
from libs.email_i18n import EmailType, get_email_i18n_service
|
||||
from models import Account, Tenant, TenantAccountJoin
|
||||
|
||||
@@ -22,6 +22,7 @@ from werkzeug.exceptions import Unauthorized
|
||||
|
||||
from configs import dify_config
|
||||
from constants.languages import get_valid_language, language_timezone_mapping
|
||||
from enums import DeploymentEdition
|
||||
from events.tenant_event import tenant_was_created
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client, redis_fallback
|
||||
@@ -119,7 +120,7 @@ class InvitationDetailDict(TypedDict):
|
||||
|
||||
def _try_join_enterprise_default_workspace(account_id: str) -> None:
|
||||
"""Best-effort join to enterprise default workspace."""
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return
|
||||
|
||||
from services.enterprise.enterprise_service import try_join_default_workspace
|
||||
@@ -378,7 +379,7 @@ class AccountService:
|
||||
payload = {
|
||||
"user_id": account.id,
|
||||
"exp": exp,
|
||||
"iss": dify_config.EDITION,
|
||||
"iss": dify_config.DEPLOYMENT_EDITION.value,
|
||||
"sub": "Console API Passport",
|
||||
}
|
||||
|
||||
@@ -463,7 +464,7 @@ class AccountService:
|
||||
if not FeatureService.get_license().seats.is_available():
|
||||
raise SeatsLimitExceededError("licensed seats limit exceeded")
|
||||
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(email):
|
||||
raise AccountRegisterError(
|
||||
description=(
|
||||
"This email account has been deleted within the past "
|
||||
@@ -1053,7 +1054,7 @@ class AccountService:
|
||||
|
||||
@classmethod
|
||||
def get_user_through_email(cls, email: str, *, session: Session):
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(email):
|
||||
raise AccountRegisterError(
|
||||
description=(
|
||||
"This email account has been deleted within the past "
|
||||
@@ -1072,7 +1073,7 @@ class AccountService:
|
||||
|
||||
@classmethod
|
||||
def is_account_in_freeze(cls, email: str) -> bool:
|
||||
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(email):
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(email):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -1395,7 +1396,7 @@ class TenantService:
|
||||
session.add(ta)
|
||||
|
||||
session.commit()
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
BillingService.clean_billing_info_cache(tenant.id)
|
||||
return ta
|
||||
|
||||
@@ -1828,7 +1829,7 @@ class TenantService:
|
||||
account_email,
|
||||
)
|
||||
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
BillingService.clean_billing_info_cache(tenant.id)
|
||||
|
||||
# Queue account deletion sync task for enterprise backend to reassign resources (enterprise only)
|
||||
|
||||
@@ -21,7 +21,7 @@ from core.app.features.rate_limiting import RateLimit
|
||||
from core.app.features.rate_limiting.rate_limit import rate_limit_context
|
||||
from core.app.layers.pause_state_persist_layer import PauseStateLayerConfig
|
||||
from core.db import session_factory
|
||||
from enums.quota_type import QuotaType
|
||||
from enums import DeploymentEdition, QuotaType
|
||||
from extensions.otel import AppGenerateHandler, trace_span
|
||||
from models.model import Account, App, AppMode, EndUser
|
||||
from models.workflow import Workflow, WorkflowRun
|
||||
@@ -134,7 +134,7 @@ class AppGenerateService:
|
||||
action: Callable[[RateLimit, str], Any],
|
||||
):
|
||||
quota_charge = unlimited()
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
try:
|
||||
quota_charge = QuotaService.reserve(QuotaType.WORKFLOW, app_model.tenant_id)
|
||||
except QuotaExceededError:
|
||||
|
||||
@@ -19,6 +19,7 @@ from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
|
||||
from core.model_manager import ModelManager
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from core.tools.utils.configuration import ToolParameterConfigurationManager
|
||||
from enums import DeploymentEdition
|
||||
from events.app_event import app_was_created, app_was_deleted, app_was_updated
|
||||
from extensions.ext_database import db # noqa: F401
|
||||
from graphon.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
|
||||
@@ -652,7 +653,7 @@ class AppService:
|
||||
# update web app setting as private
|
||||
EnterpriseService.WebAppAuth.update_app_access_mode(app.id, "private")
|
||||
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
BillingService.clean_billing_info_cache(app.tenant_id)
|
||||
|
||||
return app
|
||||
@@ -1056,7 +1057,7 @@ class AppService:
|
||||
if FeatureService.get_system_features().webapp_auth.enabled:
|
||||
EnterpriseService.WebAppAuth.cleanup_webapp(app.id)
|
||||
|
||||
if dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD:
|
||||
BillingService.clean_billing_info_cache(app.tenant_id)
|
||||
|
||||
# Trigger asynchronous deletion of app and related data
|
||||
|
||||
@@ -14,7 +14,7 @@ from celery.result import AsyncResult
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from enums.quota_type import QuotaType
|
||||
from enums import QuotaType
|
||||
from extensions.ext_database import db
|
||||
from models.account import Account
|
||||
from models.enums import CreatorUserRole, WorkflowTriggerStatus
|
||||
|
||||
@@ -12,7 +12,7 @@ from tenacity import retry, retry_if_exception_type, stop_before_delay, wait_fix
|
||||
from werkzeug.exceptions import InternalServerError
|
||||
|
||||
from core.helper.http_client_pooling import get_pooled_http_client
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from extensions.ext_redis import redis_client
|
||||
from libs.helper import RateLimiter
|
||||
from models import Account, TenantAccountJoin, TenantAccountRole
|
||||
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_storage import storage
|
||||
from graphon.model_runtime.utils.encoders import jsonable_encoder
|
||||
@@ -371,7 +371,7 @@ class ClearFreePlanTenantExpiredLogs:
|
||||
def process_tenant(flask_app: Flask, tenant_id: str):
|
||||
try:
|
||||
if (
|
||||
not dify_config.BILLING_ENABLED
|
||||
dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD
|
||||
or BillingService.get_info(tenant_id)["subscription"]["plan"] == CloudPlan.SANDBOX
|
||||
):
|
||||
# only process sandbox tenant
|
||||
|
||||
@@ -15,6 +15,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from core.errors.error import QuotaExceededError
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_redis import redis_client
|
||||
from models import TenantCreditPool
|
||||
from models.enums import ProviderQuotaType
|
||||
@@ -51,7 +52,7 @@ class CreditPoolService:
|
||||
|
||||
@staticmethod
|
||||
def _use_billing_quota() -> bool:
|
||||
return bool(dify_config.BILLING_ENABLED)
|
||||
return bool(dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD)
|
||||
|
||||
@staticmethod
|
||||
def _require_session(session: Session | None) -> Session:
|
||||
|
||||
@@ -23,7 +23,7 @@ from core.model_manager import ModelManager
|
||||
from core.rag.index_processor.constant.built_in_field import BuiltInField
|
||||
from core.rag.index_processor.constant.index_type import IndexStructureType, IndexTechniqueType
|
||||
from core.rag.retrieval.retrieval_methods import RetrievalMethod
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from events.dataset_event import dataset_was_deleted
|
||||
from events.document_event import document_was_deleted
|
||||
from extensions.ext_redis import redis_client
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Callable
|
||||
from functools import cached_property
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -8,6 +8,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_redis import redis_client
|
||||
from models.account import TenantAccountJoin
|
||||
|
||||
@@ -79,9 +80,9 @@ def sync_workspace_member_removal(workspace_id: str, member_id: str, *, source:
|
||||
source: Source of the sync request (e.g., "workspace_member_removed")
|
||||
|
||||
Returns:
|
||||
bool: True if task was queued (or skipped in community), False if queueing failed
|
||||
bool: True if task was queued (or skipped outside the Enterprise edition), False if queueing failed
|
||||
"""
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return True
|
||||
|
||||
return _queue_task(workspace_id=workspace_id, member_id=member_id, source=source)
|
||||
@@ -100,9 +101,9 @@ def sync_account_deletion(account_id: str, *, source: str, session: Session) ->
|
||||
session: SQLAlchemy session used to fetch workspace memberships
|
||||
|
||||
Returns:
|
||||
bool: True if all tasks were queued (or skipped in community), False if any queueing failed
|
||||
bool: True if all tasks were queued (or skipped outside the Enterprise edition), False if any queueing failed
|
||||
"""
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return True
|
||||
|
||||
# Fetch all workspaces the account belongs to
|
||||
|
||||
@@ -9,6 +9,7 @@ from cachetools.func import ttl_cache
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from extensions.ext_redis import redis_client
|
||||
from services.enterprise.base import (
|
||||
EnterpriseRequest,
|
||||
@@ -95,7 +96,7 @@ def try_join_default_workspace(account_id: str) -> None:
|
||||
This is a best-effort integration. Failures must not block user registration.
|
||||
"""
|
||||
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -373,9 +374,9 @@ class EnterpriseService:
|
||||
caching, every request on an expired license would hit the enterprise API.
|
||||
|
||||
Returns:
|
||||
LicenseStatus enum value, or None if enterprise is disabled / unreachable.
|
||||
LicenseStatus enum value, or None outside the Enterprise edition or when unreachable.
|
||||
"""
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return None
|
||||
|
||||
cached = cls._read_cached_license_status()
|
||||
|
||||
@@ -4,8 +4,7 @@ from enum import StrEnum
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
|
||||
|
||||
class FeatureResponseModel(BaseModel):
|
||||
@@ -18,7 +17,13 @@ class SubscriptionModel(FeatureResponseModel):
|
||||
|
||||
|
||||
class BillingModel(FeatureResponseModel):
|
||||
enabled: bool = False
|
||||
# Deprecated compatibility field. Deployment edition is the only source of truth for product edition.
|
||||
# TODO: Remove after clients migrate to `SystemFeatureModel.deployment_edition`.
|
||||
enabled: bool = Field(
|
||||
default=False,
|
||||
deprecated=True,
|
||||
description="Deprecated. Use system features deployment_edition to determine the product edition.",
|
||||
)
|
||||
subscription: SubscriptionModel = SubscriptionModel()
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Application service for Console feature queries."""
|
||||
"""Application service for feature queries exposed by API adapters."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Protocol
|
||||
|
||||
@@ -4,9 +4,7 @@ from collections.abc import Mapping
|
||||
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
||||
|
||||
from configs import dify_config
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums.hosted_provider import HostedTrialProvider
|
||||
from enums import CloudPlan, DeploymentEdition, HostedTrialProvider
|
||||
from services.billing_service import BillingInfo, BillingService
|
||||
from services.enterprise.enterprise_service import EnterpriseService
|
||||
from services.entities import feature_entities
|
||||
@@ -30,14 +28,14 @@ class FeatureService:
|
||||
|
||||
cls._fulfill_params_from_env(features)
|
||||
|
||||
if dify_config.BILLING_ENABLED and tenant_id:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and tenant_id:
|
||||
cls._fulfill_params_from_billing_api(
|
||||
features,
|
||||
tenant_id,
|
||||
exclude_vector_space=exclude_vector_space,
|
||||
)
|
||||
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
features.webapp_copyright_enabled = True
|
||||
features.knowledge_pipeline.publish_enabled = True
|
||||
cls._fulfill_params_from_workspace_info(features, tenant_id)
|
||||
@@ -52,7 +50,7 @@ class FeatureService:
|
||||
@classmethod
|
||||
def get_vector_space(cls, tenant_id: str) -> feature_entities.VectorSpaceLimitationModel:
|
||||
vector_space = feature_entities.VectorSpaceLimitationModel(size=0, limit=5)
|
||||
if dify_config.BILLING_ENABLED and tenant_id:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and tenant_id:
|
||||
billing_vector_space = BillingService.get_vector_space(tenant_id)
|
||||
# NOTE: billing API returns vector_space.size as float (e.g. 0.0),
|
||||
# but feature API keeps LimitationModel.size as int for compatibility.
|
||||
@@ -65,7 +63,7 @@ class FeatureService:
|
||||
@classmethod
|
||||
def get_knowledge_rate_limit(cls, tenant_id: str):
|
||||
knowledge_rate_limit = feature_entities.KnowledgeRateLimitModel()
|
||||
if dify_config.BILLING_ENABLED and tenant_id:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and tenant_id:
|
||||
knowledge_rate_limit.enabled = True
|
||||
limit_info = BillingService.get_knowledge_rate_limit(tenant_id)
|
||||
knowledge_rate_limit.limit = limit_info.get("limit", 10)
|
||||
@@ -75,7 +73,7 @@ class FeatureService:
|
||||
@classmethod
|
||||
def get_knowledge_file_size_limit(cls, tenant_id: str | None) -> int:
|
||||
default_limit = dify_config.UPLOAD_FILE_SIZE_LIMIT
|
||||
if not dify_config.BILLING_ENABLED or not tenant_id:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD or not tenant_id:
|
||||
return default_limit
|
||||
|
||||
billing_info = BillingService.get_info(tenant_id, exclude_vector_space=True)
|
||||
@@ -91,7 +89,7 @@ class FeatureService:
|
||||
def _resolve_human_input_email_delivery_enabled(
|
||||
cls, *, features: feature_entities.FeatureModel, tenant_id: str | None
|
||||
) -> bool:
|
||||
if dify_config.ENTERPRISE_ENABLED or not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return True
|
||||
if not tenant_id:
|
||||
return False
|
||||
@@ -107,7 +105,7 @@ class FeatureService:
|
||||
|
||||
cls._fulfill_system_params_from_env(system_features)
|
||||
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE:
|
||||
system_features.branding.enabled = True
|
||||
system_features.webapp_auth.enabled = True
|
||||
system_features.enable_change_email = False
|
||||
@@ -125,7 +123,7 @@ class FeatureService:
|
||||
def is_workspace_creation_allowed(cls) -> bool:
|
||||
"""Resolve the backend workspace-creation policy, including the Enterprise override."""
|
||||
is_allowed = dify_config.ALLOW_CREATE_WORKSPACE
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return is_allowed
|
||||
|
||||
enterprise_info = EnterpriseService.get_info()
|
||||
@@ -134,12 +132,12 @@ class FeatureService:
|
||||
@classmethod
|
||||
def is_plugin_manager_enabled(cls) -> bool:
|
||||
"""Return whether Enterprise plugin credential policies must be enforced."""
|
||||
return dify_config.ENTERPRISE_ENABLED
|
||||
return dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE
|
||||
|
||||
@classmethod
|
||||
def get_plugin_installation_permission(cls) -> feature_entities.PluginInstallationPermissionModel:
|
||||
"""Resolve the validated deployment-wide plugin installation policy."""
|
||||
if not dify_config.ENTERPRISE_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return feature_entities.PluginInstallationPermissionModel()
|
||||
|
||||
return cls._resolve_plugin_installation_permission(EnterpriseService.get_info())
|
||||
@@ -151,11 +149,10 @@ class FeatureService:
|
||||
Non-enterprise deployments have no license, so an unconstrained default
|
||||
(unlimited seats/workspaces) is returned.
|
||||
"""
|
||||
if dify_config.ENTERPRISE_ENABLED:
|
||||
license_model = cls._build_license(EnterpriseService.get_info())
|
||||
license_model.license_expiry_notice_enabled = dify_config.ENABLE_LICENSE_EXPIRY_NOTICE
|
||||
else:
|
||||
license_model = feature_entities.LicenseModel()
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.ENTERPRISE:
|
||||
return feature_entities.LicenseModel()
|
||||
license_model = cls._build_license(EnterpriseService.get_info())
|
||||
license_model.license_expiry_notice_enabled = dify_config.ENABLE_LICENSE_EXPIRY_NOTICE
|
||||
return license_model
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Feature-query gateway backed by FeatureService."""
|
||||
"""Feature-query gateway backed by the existing FeatureService."""
|
||||
|
||||
from typing import override
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from core.plugin.entities.plugin_daemon import PluginModelProviderBinding
|
||||
from core.plugin.impl.model_runtime_factory import create_plugin_model_provider_factory, create_plugin_provider_manager
|
||||
from core.plugin.plugin_service import PluginService
|
||||
from core.provider_manager import ProviderManager
|
||||
from enums import DeploymentEdition
|
||||
from extensions import ext_hosting_provider
|
||||
from graphon.model_runtime.entities.model_entities import ModelType, ParameterRule
|
||||
from models.provider import (
|
||||
@@ -296,7 +297,7 @@ class ModelProviderService:
|
||||
) -> ProviderType:
|
||||
if state.preferred_provider_type is not None:
|
||||
return state.preferred_provider_type
|
||||
if dify_config.EDITION == "CLOUD" and system_enabled:
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and system_enabled:
|
||||
return ProviderType.SYSTEM
|
||||
if custom_present:
|
||||
return ProviderType.CUSTOM
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""License gate for the /openapi/v1/permitted-external-apps* surface.
|
||||
|
||||
EE-only. CE deploys (``ENTERPRISE_ENABLED=false``) skip the gate entirely —
|
||||
Enterprise-edition only. Community and Cloud deployments skip the gate entirely —
|
||||
the EE blueprint chain is what gives CE deploys no callers on this surface
|
||||
in practice, but the explicit short-circuit avoids any test/fixture that
|
||||
flips the surface on without flipping the license.
|
||||
@@ -22,6 +22,7 @@ from functools import wraps
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from services.entities.feature_entities import LicenseStatus
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
@@ -32,12 +33,12 @@ _VALID_LICENSE_STATUSES: frozenset[LicenseStatus] = frozenset({LicenseStatus.ACT
|
||||
|
||||
def license_required[**P, R](view: Callable[P, R]) -> Callable[P, R]:
|
||||
"""Decorator form. Raises ``Forbidden('license_required')`` when the EE
|
||||
deployment has no valid license. No-op on CE (``ENTERPRISE_ENABLED=false``).
|
||||
deployment has no valid license. No-op outside the Enterprise edition.
|
||||
"""
|
||||
|
||||
@wraps(view)
|
||||
def decorated(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
if dify_config.ENTERPRISE_ENABLED and not _is_license_valid():
|
||||
if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE and not _is_license_valid():
|
||||
raise Forbidden(description="license_required")
|
||||
return view(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -3,12 +3,9 @@ from __future__ import annotations
|
||||
import logging
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from configs import dify_config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from enums.quota_type import QuotaType
|
||||
from enums import DeploymentEdition, QuotaType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -88,8 +85,6 @@ class QuotaCharge:
|
||||
|
||||
|
||||
def unlimited() -> QuotaCharge:
|
||||
from enums.quota_type import QuotaType
|
||||
|
||||
return QuotaCharge(success=True, charge_id=None, _quota_type=QuotaType.UNLIMITED)
|
||||
|
||||
|
||||
@@ -123,8 +118,8 @@ class QuotaService:
|
||||
from services.billing_service import BillingService
|
||||
from services.errors.app import QuotaExceededError
|
||||
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
logger.debug("Billing disabled, allowing request for %s", tenant_id)
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
logger.debug("Quota billing is unavailable outside the Cloud edition; allowing request for %s", tenant_id)
|
||||
return QuotaCharge(success=True, charge_id=None, _quota_type=quota_type)
|
||||
|
||||
logger.info("Reserving %d %s quota for tenant %s", amount, quota_type.value, tenant_id)
|
||||
@@ -179,7 +174,7 @@ class QuotaService:
|
||||
|
||||
@staticmethod
|
||||
def check(quota_type: QuotaType, tenant_id: str, amount: int = 1) -> bool:
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return True
|
||||
|
||||
if amount <= 0:
|
||||
@@ -198,7 +193,7 @@ class QuotaService:
|
||||
try:
|
||||
from services.billing_service import BillingService
|
||||
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return
|
||||
|
||||
if not reservation_id:
|
||||
|
||||
@@ -5,7 +5,7 @@ from functools import cached_property
|
||||
|
||||
from core.app.entities.rag_pipeline_invoke_entities import RagPipelineInvokeEntity
|
||||
from core.rag.pipeline.queue import TenantIsolatedTaskQueue
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan
|
||||
from extensions.ext_database import db
|
||||
from services.feature_service import FeatureService
|
||||
from services.file_service import FileService
|
||||
|
||||
@@ -4,7 +4,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import DeploymentEdition
|
||||
from models.model import AccountTrialAppRecord, App, TrialApp
|
||||
from services.recommend_app.recommend_app_factory import RecommendAppRetrievalFactory
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
||||
from typing import Protocol, override
|
||||
|
||||
from configs import dify_config
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from services.billing_service import BillingService, SubscriptionPlan
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -45,7 +45,7 @@ class MessagesCleanPolicy(Protocol):
|
||||
|
||||
class BillingDisabledPolicy(MessagesCleanPolicy):
|
||||
"""
|
||||
Policy for community or enterpriseedition (billing disabled).
|
||||
Policy for self-hosted editions, which do not use Cloud billing plans.
|
||||
|
||||
No special filter logic, just return all message ids.
|
||||
"""
|
||||
@@ -61,7 +61,7 @@ class BillingDisabledPolicy(MessagesCleanPolicy):
|
||||
|
||||
class BillingSandboxPolicy(MessagesCleanPolicy):
|
||||
"""
|
||||
Policy for sandbox plan tenants in cloud edition (billing enabled).
|
||||
Policy for sandbox-plan tenants in the Cloud edition.
|
||||
|
||||
Filters messages based on sandbox plan expiration rules:
|
||||
- Skip tenants in the whitelist
|
||||
@@ -186,24 +186,22 @@ def create_message_clean_policy(
|
||||
"""
|
||||
Factory function to create the appropriate message clean policy.
|
||||
|
||||
Determines which policy to use based on BILLING_ENABLED configuration:
|
||||
- If BILLING_ENABLED is True: returns BillingSandboxPolicy
|
||||
- If BILLING_ENABLED is False: returns BillingDisabledPolicy
|
||||
Cloud uses BillingSandboxPolicy; self-hosted editions use BillingDisabledPolicy.
|
||||
|
||||
Args:
|
||||
graceful_period_days: Grace period in days after subscription expiration (default: 21)
|
||||
current_timestamp: Current Unix timestamp for testing (default: None, uses current time)
|
||||
"""
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
logger.info("create_message_clean_policy: billing disabled, using BillingDisabledPolicy")
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
logger.info("create_message_clean_policy: self-hosted edition, using BillingDisabledPolicy")
|
||||
return BillingDisabledPolicy()
|
||||
|
||||
# Billing enabled - fetch whitelist from BillingService
|
||||
# Cloud deployment - fetch whitelist from BillingService.
|
||||
tenant_whitelist = BillingService.get_expired_subscription_cleanup_whitelist()
|
||||
plan_provider = BillingService.get_plan_bulk_with_cache
|
||||
|
||||
logger.info(
|
||||
"create_message_clean_policy: billing enabled, using BillingSandboxPolicy "
|
||||
"create_message_clean_policy: Cloud edition, using BillingSandboxPolicy "
|
||||
"(graceful_period_days=%s, whitelist=%s)",
|
||||
graceful_period_days,
|
||||
tenant_whitelist,
|
||||
|
||||
@@ -169,8 +169,8 @@ class MessagesCleanService:
|
||||
"""
|
||||
Service for cleaning expired messages based on retention policies.
|
||||
|
||||
Compatible with non cloud edition (billing disabled): all messages in the time range will be deleted.
|
||||
If billing is enabled: only sandbox plan tenant messages are deleted (with whitelist and grace period support).
|
||||
In self-hosted editions, all messages in the time range are deleted.
|
||||
In the Cloud edition, only sandbox-plan tenant messages are deleted, with whitelist and grace-period support.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -46,7 +46,7 @@ from sqlalchemy import inspect, select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from graphon.enums import WorkflowType
|
||||
from libs.archive_storage import (
|
||||
@@ -535,8 +535,8 @@ class WorkflowRunArchiver:
|
||||
if self.paid_tenant_ids is not None:
|
||||
return tenant_ids & self.paid_tenant_ids
|
||||
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
# If billing is not enabled, treat all tenants as paid
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
# Self-hosted editions have no Cloud billing plans, so treat all tenants as paid.
|
||||
return tenant_ids
|
||||
|
||||
if not tenant_ids:
|
||||
|
||||
@@ -16,7 +16,7 @@ import click
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_database import db
|
||||
from repositories.api_workflow_run_repository import (
|
||||
APIWorkflowRunRepository,
|
||||
@@ -478,7 +478,7 @@ class WorkflowRunCleanup:
|
||||
def _filter_free_tenants(self, tenant_ids: Iterable[str]) -> set[str]:
|
||||
tenant_id_list = sorted(set(tenant_ids))
|
||||
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
return set(tenant_id_list)
|
||||
|
||||
if not tenant_id_list:
|
||||
@@ -538,7 +538,7 @@ class WorkflowRunCleanup:
|
||||
if self._cleanup_whitelist is not None:
|
||||
return self._cleanup_whitelist
|
||||
|
||||
if not dify_config.BILLING_ENABLED:
|
||||
if dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD:
|
||||
self._cleanup_whitelist = set()
|
||||
return self._cleanup_whitelist
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ from typing import Any
|
||||
from sqlalchemy import delete, event, func, select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from configs import dify_config
|
||||
from core.workflow.node_factory import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
|
||||
from enums import DeploymentEdition
|
||||
from graphon.enums import BuiltinNodeTypes, NodeType
|
||||
from libs.infinite_scroll_pagination import InfiniteScrollPagination
|
||||
from models import Account, TagBinding
|
||||
@@ -150,10 +152,9 @@ class SnippetService:
|
||||
|
||||
@staticmethod
|
||||
def _delete_archived_workflow_run_files(*, snippet: CustomizedSnippet) -> None:
|
||||
from configs import dify_config
|
||||
from libs.archive_storage import ArchiveStorageNotConfiguredError, get_archive_storage
|
||||
|
||||
if not (dify_config.BILLING_ENABLED and dify_config.ARCHIVE_STORAGE_ENABLED):
|
||||
if not (dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and dify_config.ARCHIVE_STORAGE_ENABLED):
|
||||
return
|
||||
|
||||
prefix = f"{snippet.tenant_id}/app_id={snippet.id}/"
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from configs import dify_config
|
||||
from enums import DeploymentEdition
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.model import DifySetup
|
||||
|
||||
@@ -74,8 +75,7 @@ class CommunityTelemetryService:
|
||||
@classmethod
|
||||
def _is_enabled(cls) -> bool:
|
||||
return (
|
||||
dify_config.EDITION == "SELF_HOSTED"
|
||||
and not dify_config.ENTERPRISE_ENABLED
|
||||
dify_config.DEPLOYMENT_EDITION == DeploymentEdition.COMMUNITY
|
||||
and not dify_config.DISABLE_TELEMETRY
|
||||
and not dify_config.DO_NOT_TRACK
|
||||
and not dify_config.CI
|
||||
@@ -88,7 +88,7 @@ class CommunityTelemetryService:
|
||||
"event": event,
|
||||
"instance_id": setup.instance_id or "",
|
||||
"version": setup.version if event == "install" else dify_config.project.version,
|
||||
"edition": dify_config.EDITION,
|
||||
"edition": dify_config.DEPLOYMENT_EDITION.value,
|
||||
"deployment_type": "unknown",
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"os": cls._normalize_os(platform.system()),
|
||||
|
||||
@@ -31,6 +31,7 @@ from core.tools.utils.system_encryption import decrypt_system_params
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from models.account import Account
|
||||
from models.enums import PermissionEnum
|
||||
from models.provider_ids import ToolProviderID
|
||||
from models.tools import BuiltinToolProvider, ToolOAuthSystemClient, ToolOAuthTenantClient
|
||||
from services.tools.tools_transform_service import ToolTransformService
|
||||
@@ -282,8 +283,6 @@ class BuiltinToolManageService:
|
||||
cache=NoOpProviderCredentialCache(),
|
||||
)
|
||||
|
||||
from models.enums import PermissionEnum
|
||||
|
||||
visibility_enum = PermissionEnum(visibility) if visibility else PermissionEnum.ALL_TEAM
|
||||
# Plugin credentials only expose only_me / all_team_members at creation;
|
||||
# partial-member access is handled by workspace RBAC, not per-credential.
|
||||
|
||||
@@ -259,7 +259,7 @@ class MCPToolManageService:
|
||||
mcp_provider.encrypted_credentials = self._process_credentials(authentication, mcp_provider, tenant_id)
|
||||
|
||||
# Update user-identity forwarding mode. The controller has already
|
||||
# resolved "leave unchanged" and applied the ENTERPRISE_ENABLED gate,
|
||||
# resolved "leave unchanged" and applied the Enterprise-edition gate,
|
||||
# so this is always a concrete, vetted value.
|
||||
mcp_provider.identity_mode = identity_mode
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from core.workflow.nodes.trigger_webhook.entities import (
|
||||
WebhookData,
|
||||
WebhookParameter,
|
||||
)
|
||||
from enums.quota_type import QuotaType
|
||||
from enums import QuotaType
|
||||
from extensions.ext_database import db
|
||||
from extensions.ext_redis import redis_client
|
||||
from factories import file_factory
|
||||
|
||||
@@ -15,8 +15,7 @@ from core.rag.datasource.vdb.vector_type import VectorType
|
||||
from core.rag.embedding.cached_embedding import CacheEmbedding
|
||||
from core.rag.index_processor.constant.index_type import IndexStructureType, IndexTechniqueType
|
||||
from core.rag.models.document import Document
|
||||
from enums.cloud_plan import CloudPlan
|
||||
from enums.deployment_edition import DeploymentEdition
|
||||
from enums import CloudPlan, DeploymentEdition
|
||||
from extensions.ext_redis import redis_client
|
||||
from graphon.model_runtime.entities.model_entities import ModelType
|
||||
from models.dataset import Dataset
|
||||
@@ -248,7 +247,6 @@ class VectorSpaceAdmissionService:
|
||||
) -> None:
|
||||
if (
|
||||
dify_config.DEPLOYMENT_EDITION != DeploymentEdition.CLOUD
|
||||
or not dify_config.BILLING_ENABLED
|
||||
or dataset.indexing_technique != IndexTechniqueType.HIGH_QUALITY
|
||||
or workload.total_points == 0
|
||||
or workload.probe_text is None
|
||||
|
||||
Loaded 100 of 250 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user