From ba76312248fd43caf23192ee8b671c95efb2e096 Mon Sep 17 00:00:00 2001 From: Stream Date: Wed, 27 Aug 2025 16:12:40 +0800 Subject: [PATCH 1/2] feat: adapt to plugin_daemon endpoint --- api/controllers/console/workspace/plugin.py | 22 ++++++++++++++++++ api/core/plugin/entities/plugin_daemon.py | 4 ++++ api/core/plugin/impl/plugin.py | 25 +++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index fd5421fa64..572723ff65 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -643,8 +643,30 @@ class PluginAutoUpgradeExcludePluginApi(Resource): return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args["plugin_id"])}) +class PluginReadmeApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self): + tenant_id = current_user.current_tenant_id + parser = reqparse.RequestParser() + parser.add_argument("plugin_unique_identifier", type=str, required=True, location="args") + parser.add_argument("language", type=str, required=False, location="args") + args = parser.parse_args() + return jsonable_encoder( + { + "readme": PluginService.fetch_plugin_readme( + tenant_id, + args["plugin_unique_identifier"], + args.get("language", "en-US") + ) + } + ) + + api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") api.add_resource(PluginListApi, "/workspaces/current/plugin/list") +api.add_resource(PluginReadmeApi, "/workspaces/current/plugin/readme") api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids") api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py index f1d6860bb4..ecbdd7ab17 100644 --- a/api/core/plugin/entities/plugin_daemon.py +++ b/api/core/plugin/entities/plugin_daemon.py @@ -196,3 +196,7 @@ class PluginListResponse(BaseModel): class PluginDynamicSelectOptionsResponse(BaseModel): options: Sequence[PluginParameterOption] = Field(description="The options of the dynamic select.") + +class PluginReadmeResponse(BaseModel): + content: str = Field(description="The readme of the plugin.") + language: str = Field(description="The language of the readme.") diff --git a/api/core/plugin/impl/plugin.py b/api/core/plugin/impl/plugin.py index 04ac8c9649..ab0b1c15ab 100644 --- a/api/core/plugin/impl/plugin.py +++ b/api/core/plugin/impl/plugin.py @@ -1,5 +1,7 @@ from collections.abc import Sequence +from requests import HTTPError + from core.plugin.entities.bundle import PluginBundleDependency from core.plugin.entities.plugin import ( GenericProviderID, @@ -14,11 +16,34 @@ from core.plugin.entities.plugin_daemon import ( PluginInstallTask, PluginInstallTaskStartResponse, PluginListResponse, + PluginReadmeResponse, ) from core.plugin.impl.base import BasePluginClient class PluginInstaller(BasePluginClient): + def fetch_plugin_readme(self, tenant_id: str, plugin_unique_identifier: str, language: str) -> str: + """ + Fetch plugin readme + """ + try: + response = self._request_with_plugin_daemon_response( + "GET", + f"plugin/{tenant_id}/management/fetch/readme", + PluginReadmeResponse, + params={ + "tenant_id":tenant_id, + "plugin_unique_identifier": plugin_unique_identifier, + "language": language + } + ) + return response.content + except HTTPError as e: + message = e.args[0] + if "404" in message: + return "" + raise e + def fetch_plugin_by_identifier( self, tenant_id: str, From 94ecbd44e46d128d6687951010c387bc3aceed52 Mon Sep 17 00:00:00 2001 From: Stream Date: Wed, 27 Aug 2025 20:03:59 +0800 Subject: [PATCH 2/2] feat: add API endpoint to extract plugin assets --- api/controllers/console/workspace/plugin.py | 17 +++++++++++++++++ api/core/plugin/impl/asset.py | 6 ++++++ api/services/plugin/plugin_service.py | 13 +++++++++++++ 3 files changed, 36 insertions(+) diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 572723ff65..27adf7662b 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -107,6 +107,22 @@ class PluginIconApi(Resource): icon_cache_max_age = dify_config.TOOL_ICON_CACHE_MAX_AGE return send_file(io.BytesIO(icon_bytes), mimetype=mimetype, max_age=icon_cache_max_age) +class PluginAssetApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self): + req = reqparse.RequestParser() + req.add_argument("plugin_unique_identifier", type=str, required=True, location="args") + req.add_argument("file_name", type=str, required=True, location="args") + args = req.parse_args() + + tenant_id = current_user.current_tenant_id + try: + binary = PluginService.extract_asset(tenant_id, args["plugin_unique_identifier"], args["file_name"]) + return send_file(io.BytesIO(binary), mimetype="application/octet-stream") + except PluginDaemonClientSideError as e: + raise ValueError(e) class PluginUploadFromPkgApi(Resource): @setup_required @@ -670,6 +686,7 @@ api.add_resource(PluginReadmeApi, "/workspaces/current/plugin/readme") api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids") api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") +api.add_resource(PluginAssetApi, "/workspaces/current/plugin/asset") api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg") api.add_resource(PluginUploadFromGithubApi, "/workspaces/current/plugin/upload/github") api.add_resource(PluginUploadFromBundleApi, "/workspaces/current/plugin/upload/bundle") diff --git a/api/core/plugin/impl/asset.py b/api/core/plugin/impl/asset.py index b9bfe2d2cf..0c6a744bb5 100644 --- a/api/core/plugin/impl/asset.py +++ b/api/core/plugin/impl/asset.py @@ -10,3 +10,9 @@ class PluginAssetManager(BasePluginClient): if response.status_code != 200: raise ValueError(f"can not found asset {id}") return response.content + + def extract_asset(self, tenant_id: str, plugin_unique_identifier: str, filename: str) -> bytes: + response = self._request(method="GET", path=f"plugin/{tenant_id}/asset/{plugin_unique_identifier}") + if response.status_code != 200: + raise ValueError(f"can not found asset {plugin_unique_identifier}, {str(response.status_code)}") + return response.content diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index 9005f0669b..4c0da761d7 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -186,6 +186,11 @@ class PluginService: mime_type, _ = guess_type(asset_file) return manager.fetch_asset(tenant_id, asset_file), mime_type or "application/octet-stream" + @staticmethod + def extract_asset(tenant_id: str, plugin_unique_identifier: str, file_name: str) -> bytes: + manager = PluginAssetManager() + return manager.extract_asset(tenant_id, plugin_unique_identifier, file_name) + @staticmethod def check_plugin_unique_identifier(tenant_id: str, plugin_unique_identifier: str) -> bool: """ @@ -492,3 +497,11 @@ class PluginService: """ manager = PluginInstaller() return manager.check_tools_existence(tenant_id, provider_ids) + + @staticmethod + def fetch_plugin_readme(tenant_id: str, plugin_unique_identifier: str, language: str) -> str: + """ + Fetch plugin readme + """ + manager = PluginInstaller() + return manager.fetch_plugin_readme(tenant_id, plugin_unique_identifier, language)