feat(api): isolate credentials from local packages (#39957)

This commit is contained in:
林玮 (Jade Lin)
2026-08-04 02:50:04 +00:00
committed by GitHub
parent 1b7a132afa
commit be0e2c03e4
8 changed files with 349 additions and 33 deletions
+56 -13
View File
@@ -48,6 +48,7 @@ from core.plugin.entities.plugin_daemon import (
PluginInstallTaskStatus,
PluginListResponse,
PluginListWithoutTotalResponse,
PluginModelProviderDeclaration,
PluginModelProviderEntity,
PluginVerification,
)
@@ -58,7 +59,6 @@ from core.plugin.impl.model import PluginModelClient
from core.plugin.impl.plugin import PluginInstaller
from extensions.ext_database import db
from extensions.ext_redis import redis_client
from graphon.model_runtime.entities.provider_entities import ProviderEntity
from models.provider import Provider, ProviderCredential, TenantPreferredModelProvider
from models.provider_ids import GenericProviderID, ModelProviderID
from services.enterprise.plugin_manager_service import (
@@ -69,7 +69,9 @@ from services.errors.plugin import PluginInstallationForbiddenError
from services.feature_service import FeatureService, PluginInstallationPermissionModel, PluginInstallationScope
logger = logging.getLogger(__name__)
_provider_entities_adapter: TypeAdapter[list[ProviderEntity]] = TypeAdapter(list[ProviderEntity])
_provider_entities_adapter: TypeAdapter[list[PluginModelProviderDeclaration]] = TypeAdapter(
list[PluginModelProviderDeclaration]
)
class _RedisLock(Protocol):
@@ -146,11 +148,44 @@ class PluginService:
return provider.provider
@classmethod
def _to_provider_entity(cls, provider: PluginModelProviderEntity) -> ProviderEntity:
declaration = provider.declaration.model_copy(deep=True)
declaration.provider = f"{provider.plugin_id}/{provider.provider}"
declaration.provider_name = cls._get_provider_short_name_alias(provider)
return declaration
def _to_provider_entity(
cls,
provider: PluginModelProviderEntity,
installation_source: PluginInstallationSource | None,
) -> PluginModelProviderDeclaration:
return PluginModelProviderDeclaration.model_validate(
{
**provider.declaration.model_dump(),
"provider": f"{provider.plugin_id}/{provider.provider}",
"provider_name": cls._get_provider_short_name_alias(provider),
"plugin_unique_identifier": provider.plugin_unique_identifier,
"installation_source": installation_source,
}
)
@classmethod
def _resolve_model_provider_installation_sources(
cls,
tenant_id: str,
providers: Sequence[PluginModelProviderEntity],
) -> Mapping[str, PluginInstallationSource]:
unresolved_plugin_ids = list(
dict.fromkeys(provider.plugin_id for provider in providers if provider.installation_source is None)
)
if not unresolved_plugin_ids:
return {}
try:
installations = cls.list_installations_from_ids(tenant_id, unresolved_plugin_ids)
except Exception:
logger.warning(
"Failed to resolve model provider installation sources for tenant %s.",
tenant_id,
exc_info=True,
)
return {}
return {installation.plugin_unique_identifier: installation.source for installation in installations}
@classmethod
def _encode_plugin_model_providers_cache_payload(cls, payload: bytes) -> bytes:
@@ -206,7 +241,7 @@ class PluginService:
@classmethod
def _load_cached_plugin_model_providers_for_generation(
cls, tenant_id: str, generation: int | None
) -> tuple[tuple[ProviderEntity, ...] | None, bool]:
) -> tuple[tuple[PluginModelProviderDeclaration, ...] | None, bool]:
if generation is None:
return None, False
@@ -253,7 +288,7 @@ class PluginService:
@classmethod
def _store_cached_plugin_model_providers(
cls, tenant_id: str, generation: int, providers: Sequence[ProviderEntity]
cls, tenant_id: str, generation: int, providers: Sequence[PluginModelProviderDeclaration]
) -> None:
cache_key = cls._get_plugin_model_providers_cache_key(tenant_id, generation)
try:
@@ -437,14 +472,22 @@ class PluginService:
@classmethod
def _fetch_plugin_model_providers_uncached(
cls, tenant_id: str, client: PluginModelClient | None
) -> tuple[ProviderEntity, ...]:
) -> tuple[PluginModelProviderDeclaration, ...]:
model_client = client or PluginModelClient()
return tuple(cls._to_provider_entity(provider) for provider in model_client.fetch_model_providers(tenant_id))
providers = model_client.fetch_model_providers(tenant_id)
installation_sources = cls._resolve_model_provider_installation_sources(tenant_id, providers)
return tuple(
cls._to_provider_entity(
provider,
provider.installation_source or installation_sources.get(provider.plugin_unique_identifier),
)
for provider in providers
)
@classmethod
def _fetch_and_cache_plugin_model_providers(
cls, tenant_id: str, client: PluginModelClient | None, *, refresh_generation: int | None
) -> tuple[ProviderEntity, ...]:
) -> tuple[PluginModelProviderDeclaration, ...]:
providers = cls._fetch_plugin_model_providers_uncached(tenant_id, client)
generation = cls._load_plugin_model_providers_generation(tenant_id)
if generation is not None and generation == refresh_generation:
@@ -467,7 +510,7 @@ class PluginService:
@classmethod
def fetch_plugin_model_providers(
cls, *, tenant_id: str, client: PluginModelClient | None = None
) -> Sequence[ProviderEntity]:
) -> Sequence[PluginModelProviderDeclaration]:
"""
Fetch plugin model providers through the tenant-scoped plugin cache.