diff --git a/api/core/helper/marketplace.py b/api/core/helper/marketplace.py index df4e19f343..5402d183ed 100644 --- a/api/core/helper/marketplace.py +++ b/api/core/helper/marketplace.py @@ -1,7 +1,11 @@ +from collections.abc import Sequence + +import requests from yarl import URL from configs import dify_config from core.helper.download import download_with_size_limit +from core.plugin.entities.marketplace import MarketplacePluginDeclaration def get_plugin_pkg_url(plugin_unique_identifier: str): @@ -13,3 +17,13 @@ def get_plugin_pkg_url(plugin_unique_identifier: str): def download_plugin_pkg(plugin_unique_identifier: str): url = str(get_plugin_pkg_url(plugin_unique_identifier)) return download_with_size_limit(url, dify_config.PLUGIN_MAX_PACKAGE_SIZE) + + +def batch_fetch_plugin_manifests(plugin_ids: list[str]) -> Sequence[MarketplacePluginDeclaration]: + if len(plugin_ids) == 0: + return [] + + url = str(URL(str(dify_config.MARKETPLACE_API_URL)) / "api/v1/plugins/batch") + response = requests.get(url, json={"plugin_ids": plugin_ids}) + response.raise_for_status() + return [MarketplacePluginDeclaration(**plugin) for plugin in response.json()["plugins"]] diff --git a/api/core/plugin/entities/marketplace.py b/api/core/plugin/entities/marketplace.py new file mode 100644 index 0000000000..6745e29d16 --- /dev/null +++ b/api/core/plugin/entities/marketplace.py @@ -0,0 +1,34 @@ +from typing import Optional + +from pydantic import BaseModel, Field + +from core.model_runtime.entities.provider_entities import ProviderEntity +from core.plugin.entities.endpoint import EndpointProviderDeclaration +from core.plugin.entities.plugin import PluginResourceRequirements +from core.tools.entities.common_entities import I18nObject +from core.tools.entities.tool_entities import ToolProviderEntity + + +class MarketplacePluginDeclaration(BaseModel): + name: str = Field(..., description="Unique identifier for the plugin within the marketplace") + org: str = Field(..., description="Organization or developer responsible for creating and maintaining the plugin") + plugin_id: str = Field(..., description="Globally unique identifier for the plugin across all marketplaces") + icon: str = Field(..., description="URL or path to the plugin's visual representation") + label: I18nObject = Field(..., description="Localized display name for the plugin in different languages") + brief: I18nObject = Field(..., description="Short, localized description of the plugin's functionality") + resource: PluginResourceRequirements = Field( + ..., description="Specification of computational resources needed to run the plugin" + ) + endpoint: Optional[EndpointProviderDeclaration] = Field( + None, description="Configuration for the plugin's API endpoint, if applicable" + ) + model: Optional[ProviderEntity] = Field(None, description="Details of the AI model used by the plugin, if any") + tool: Optional[ToolProviderEntity] = Field( + None, description="Information about the tool functionality provided by the plugin, if any" + ) + latest_version: str = Field( + ..., description="Most recent version number of the plugin available in the marketplace" + ) + latest_package_identifier: str = Field( + ..., description="Unique identifier for the latest package release of the plugin" + ) diff --git a/api/core/plugin/entities/plugin.py b/api/core/plugin/entities/plugin.py index db278bc371..e611871f54 100644 --- a/api/core/plugin/entities/plugin.py +++ b/api/core/plugin/entities/plugin.py @@ -105,6 +105,7 @@ class PluginEntity(BasePluginEntity): endpoints_active: int runtime_type: str version: str + latest_version: Optional[str] = None source: PluginInstallationSource meta: Mapping[str, Any] diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index 16daf3f584..294cab9139 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -2,6 +2,7 @@ from collections.abc import Sequence from mimetypes import guess_type from configs import dify_config +from core.helper import marketplace from core.helper.download import download_with_size_limit from core.helper.marketplace import download_plugin_pkg from core.plugin.entities.plugin import PluginDeclaration, PluginEntity, PluginInstallationSource @@ -26,7 +27,16 @@ class PluginService: list all plugins of the tenant """ manager = PluginInstallationManager() - return manager.list_plugins(tenant_id) + plugins = manager.list_plugins(tenant_id) + plugin_ids = [plugin.plugin_id for plugin in plugins if plugin.source == PluginInstallationSource.Marketplace] + manifests = {manifest.plugin_id: manifest for manifest in marketplace.batch_fetch_plugin_manifests(plugin_ids)} + for plugin in plugins: + if plugin.source == PluginInstallationSource.Marketplace: + if plugin.plugin_id in manifests: + # set latest_version + plugin.latest_version = manifests[plugin.plugin_id].latest_version + + return plugins @staticmethod def get_asset(tenant_id: str, asset_file: str) -> tuple[bytes, str]: