[Refactor] Proxy: move projects management to enterprise package
Remove the /project/* management endpoints and the enable_projects_ui admin-settings flag from the OSS litellm package. Project endpoints now live under litellm_enterprise and are wired through the existing enterprise router; OSS builds return 404 for every /project/* route. The enable_projects_ui UI flag is registered back onto UISettings via a small extension registry when the enterprise package is imported, so the admin toggle and downstream key/sidebar gating continue to work in enterprise builds. On OSS, explicit PATCH attempts with the flag return 403 with a clear enterprise-only message instead of being silently dropped. Pydantic request/response types (NewProjectRequest, UpdateProjectRequest, DeleteProjectRequest, NewProjectResponse) stay in litellm/proxy/_types.py because management_endpoints/common_utils.py and pydantic-shape tests import them. LiteLLM_ProjectTable and all FK columns in schema.prisma are unchanged.
This commit is contained in:
parent
e64d98f725
commit
d3a1f63af2
@ -4,10 +4,13 @@ from litellm_enterprise.enterprise_callbacks.send_emails.endpoints import (
|
||||
router as email_events_router,
|
||||
)
|
||||
|
||||
from . import ui_crud_endpoints # side-effect: registers extra UI settings
|
||||
from .audit_logging_endpoints import router as audit_logging_router
|
||||
from .management_endpoints import management_endpoints_router
|
||||
from .utils import _should_block_robots
|
||||
|
||||
__all__ = ["router", "ui_crud_endpoints"]
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(email_events_router)
|
||||
router.include_router(audit_logging_router)
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .internal_user_endpoints import router as internal_user_endpoints_router
|
||||
from .project_endpoints import router as project_endpoints_router
|
||||
|
||||
management_endpoints_router = APIRouter()
|
||||
management_endpoints_router.include_router(internal_user_endpoints_router)
|
||||
management_endpoints_router.include_router(project_endpoints_router)
|
||||
|
||||
__all__ = ["management_endpoints_router"]
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
from . import ui_settings_extensions # side-effect: registers extra UI settings fields
|
||||
|
||||
__all__ = ["ui_settings_extensions"]
|
||||
@ -0,0 +1,24 @@
|
||||
"""Enterprise-only UI settings fields.
|
||||
|
||||
Registers additional fields onto the OSS ``UISettings`` model at import time.
|
||||
Importing this module has the side effect of extending both the GET schema
|
||||
and the PATCH allowlist served by ``/get/ui_settings`` and
|
||||
``/update/ui_settings``.
|
||||
"""
|
||||
from pydantic import Field
|
||||
|
||||
from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import (
|
||||
register_extra_ui_setting,
|
||||
)
|
||||
|
||||
register_extra_ui_setting(
|
||||
"enable_projects_ui",
|
||||
bool,
|
||||
Field(
|
||||
default=False,
|
||||
description=(
|
||||
"If enabled, shows the Projects feature in the UI sidebar and "
|
||||
"the project field in key management."
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -407,9 +407,6 @@ from litellm.proxy.management_endpoints.organization_endpoints import (
|
||||
router as organization_router,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.policy_endpoints import router as policy_router
|
||||
from litellm.proxy.management_endpoints.project_endpoints import (
|
||||
router as project_router,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.router_settings_endpoints import (
|
||||
router as router_settings_router,
|
||||
)
|
||||
@ -13911,7 +13908,6 @@ app.include_router(team_router)
|
||||
app.include_router(ui_sso_router)
|
||||
app.include_router(scim_router)
|
||||
app.include_router(organization_router)
|
||||
app.include_router(project_router)
|
||||
app.include_router(customer_router)
|
||||
app.include_router(spend_management_router)
|
||||
app.include_router(cloudzero_router)
|
||||
|
||||
@ -4,6 +4,8 @@ from typing import Any, Dict, List, Optional, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from pydantic import ConfigDict, create_model
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
@ -75,6 +77,8 @@ class UIThemeSettingsResponse(SettingsResponse):
|
||||
class UISettings(BaseModel):
|
||||
"""Configuration for UI-specific flags"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
disable_model_add_for_internal_users: bool = Field(
|
||||
default=False,
|
||||
description="If true, internal users cannot add models from the UI",
|
||||
@ -100,11 +104,6 @@ class UISettings(BaseModel):
|
||||
description="If enabled, forwards client headers (e.g. Authorization) to the LLM API. Required for Claude Code with Max subscription.",
|
||||
)
|
||||
|
||||
enable_projects_ui: bool = Field(
|
||||
default=False,
|
||||
description="If enabled, shows the Projects feature in the UI sidebar and the project field in key management.",
|
||||
)
|
||||
|
||||
disable_agents_for_internal_users: bool = Field(
|
||||
default=False,
|
||||
description="If true, internal users cannot access agent management endpoints or the Agents page in the UI.",
|
||||
@ -149,7 +148,6 @@ ALLOWED_UI_SETTINGS_FIELDS = {
|
||||
"enabled_ui_pages_internal_users",
|
||||
"require_auth_for_public_ai_hub",
|
||||
"forward_client_headers_to_llm_api",
|
||||
"enable_projects_ui",
|
||||
"disable_agents_for_internal_users",
|
||||
"allow_agents_for_team_admins",
|
||||
"disable_vector_stores_for_internal_users",
|
||||
@ -168,6 +166,36 @@ _RUNTIME_GENERAL_SETTINGS_FLAGS = [
|
||||
"allow_vector_stores_for_team_admins",
|
||||
]
|
||||
|
||||
# Extension point: packages outside OSS (e.g. litellm_enterprise) can
|
||||
# contribute additional UI settings fields at import time. Each entry
|
||||
# maps a field name to a (type, FieldInfo) tuple suitable for pydantic's
|
||||
# create_model. Registering a field also appends it to
|
||||
# ALLOWED_UI_SETTINGS_FIELDS so GET/PATCH pass it through.
|
||||
_EXTRA_UI_SETTINGS_FIELDS: Dict[str, tuple] = {}
|
||||
|
||||
# Settings OSS knows about as enterprise-gated. If a caller sends one of
|
||||
# these keys and no extension package has registered it, the PATCH
|
||||
# endpoint returns 403 instead of silently dropping the value, so the
|
||||
# client gets a clear signal that the feature requires LiteLLM Enterprise.
|
||||
_ENTERPRISE_ONLY_UI_SETTINGS: set[str] = {"enable_projects_ui"}
|
||||
|
||||
|
||||
def register_extra_ui_setting(name: str, type_: Any, field: FieldInfo) -> None:
|
||||
"""Register an additional UI settings field contributed by an extension package."""
|
||||
_EXTRA_UI_SETTINGS_FIELDS[name] = (type_, field)
|
||||
ALLOWED_UI_SETTINGS_FIELDS.add(name)
|
||||
|
||||
|
||||
def _get_effective_ui_settings_class() -> type:
|
||||
"""Return UISettings with any extension-registered fields merged in."""
|
||||
if not _EXTRA_UI_SETTINGS_FIELDS:
|
||||
return UISettings
|
||||
return create_model(
|
||||
"EffectiveUISettings",
|
||||
__base__=UISettings,
|
||||
**_EXTRA_UI_SETTINGS_FIELDS,
|
||||
)
|
||||
|
||||
|
||||
class MCPSemanticFilterSettings(BaseModel):
|
||||
"""Configuration for MCP Semantic Tool Filter"""
|
||||
@ -1136,7 +1164,7 @@ async def get_ui_settings():
|
||||
|
||||
return await _get_settings_with_schema(
|
||||
settings_key="ui_settings",
|
||||
settings_class=UISettings,
|
||||
settings_class=_get_effective_ui_settings_class(),
|
||||
config=config,
|
||||
)
|
||||
|
||||
@ -1177,6 +1205,23 @@ async def update_ui_settings(
|
||||
# Only include fields the caller actually sent (not Pydantic defaults).
|
||||
settings_dict = settings.model_dump(exclude_unset=True)
|
||||
|
||||
# Reject enterprise-only settings up front so the caller gets a clear
|
||||
# signal instead of a silent drop.
|
||||
blocked_enterprise_keys = sorted(
|
||||
(settings_dict.keys() & _ENTERPRISE_ONLY_UI_SETTINGS)
|
||||
- ALLOWED_UI_SETTINGS_FIELDS
|
||||
)
|
||||
if blocked_enterprise_keys:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": (
|
||||
f"Setting(s) {blocked_enterprise_keys} are a LiteLLM "
|
||||
"Enterprise feature and are not available on this build."
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
# Enforce allowlist and drop anything unexpected
|
||||
incoming = {
|
||||
k: v for k, v in settings_dict.items() if k in ALLOWED_UI_SETTINGS_FIELDS
|
||||
|
||||
@ -20,7 +20,7 @@ from litellm._logging import verbose_proxy_logger
|
||||
from litellm.proxy.management_endpoints.team_endpoints import (
|
||||
new_team,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.project_endpoints import (
|
||||
from litellm_enterprise.proxy.management_endpoints.project_endpoints import (
|
||||
new_project,
|
||||
update_project,
|
||||
delete_project,
|
||||
@ -284,22 +284,24 @@ export default function UISettings() {
|
||||
</Space>
|
||||
</Space>
|
||||
|
||||
<Space align="start" size="middle">
|
||||
<Switch
|
||||
checked={Boolean(values.enable_projects_ui)}
|
||||
disabled={isUpdating}
|
||||
loading={isUpdating}
|
||||
onChange={handleToggleEnableProjectsUI}
|
||||
aria-label={enableProjectsUIProperty?.description ?? "Enable Projects UI"}
|
||||
/>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Typography.Text strong>[BETA] Enable Projects (page will refresh)</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
{enableProjectsUIProperty?.description ??
|
||||
"If enabled, shows the Projects feature in the UI sidebar and the project field in key management."}
|
||||
</Typography.Text>
|
||||
{enableProjectsUIProperty && (
|
||||
<Space align="start" size="middle">
|
||||
<Switch
|
||||
checked={Boolean(values.enable_projects_ui)}
|
||||
disabled={isUpdating}
|
||||
loading={isUpdating}
|
||||
onChange={handleToggleEnableProjectsUI}
|
||||
aria-label={enableProjectsUIProperty.description ?? "Enable Projects UI"}
|
||||
/>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Typography.Text strong>[BETA] Enable Projects (page will refresh)</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
{enableProjectsUIProperty.description ??
|
||||
"If enabled, shows the Projects feature in the UI sidebar and the project field in key management."}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</Space>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user