Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. import io
  2. from flask import request, send_file
  3. from flask_login import current_user
  4. from flask_restful import Resource, reqparse
  5. from werkzeug.exceptions import Forbidden
  6. from configs import dify_config
  7. from controllers.console import api
  8. from controllers.console.workspace import plugin_permission_required
  9. from controllers.console.wraps import account_initialization_required, setup_required
  10. from core.model_runtime.utils.encoders import jsonable_encoder
  11. from core.plugin.impl.exc import PluginDaemonClientSideError
  12. from libs.login import login_required
  13. from models.account import TenantPluginPermission
  14. from services.plugin.plugin_parameter_service import PluginParameterService
  15. from services.plugin.plugin_permission_service import PluginPermissionService
  16. from services.plugin.plugin_service import PluginService
  17. class PluginDebuggingKeyApi(Resource):
  18. @setup_required
  19. @login_required
  20. @account_initialization_required
  21. @plugin_permission_required(debug_required=True)
  22. def get(self):
  23. tenant_id = current_user.current_tenant_id
  24. try:
  25. return {
  26. "key": PluginService.get_debugging_key(tenant_id),
  27. "host": dify_config.PLUGIN_REMOTE_INSTALL_HOST,
  28. "port": dify_config.PLUGIN_REMOTE_INSTALL_PORT,
  29. }
  30. except PluginDaemonClientSideError as e:
  31. raise ValueError(e)
  32. class PluginListApi(Resource):
  33. @setup_required
  34. @login_required
  35. @account_initialization_required
  36. def get(self):
  37. tenant_id = current_user.current_tenant_id
  38. parser = reqparse.RequestParser()
  39. parser.add_argument("page", type=int, required=False, location="args", default=1)
  40. parser.add_argument("page_size", type=int, required=False, location="args", default=256)
  41. args = parser.parse_args()
  42. try:
  43. plugins_with_total = PluginService.list_with_total(tenant_id, args["page"], args["page_size"])
  44. except PluginDaemonClientSideError as e:
  45. raise ValueError(e)
  46. return jsonable_encoder({"plugins": plugins_with_total.list, "total": plugins_with_total.total})
  47. class PluginListLatestVersionsApi(Resource):
  48. @setup_required
  49. @login_required
  50. @account_initialization_required
  51. def post(self):
  52. req = reqparse.RequestParser()
  53. req.add_argument("plugin_ids", type=list, required=True, location="json")
  54. args = req.parse_args()
  55. try:
  56. versions = PluginService.list_latest_versions(args["plugin_ids"])
  57. except PluginDaemonClientSideError as e:
  58. raise ValueError(e)
  59. return jsonable_encoder({"versions": versions})
  60. class PluginListInstallationsFromIdsApi(Resource):
  61. @setup_required
  62. @login_required
  63. @account_initialization_required
  64. def post(self):
  65. tenant_id = current_user.current_tenant_id
  66. parser = reqparse.RequestParser()
  67. parser.add_argument("plugin_ids", type=list, required=True, location="json")
  68. args = parser.parse_args()
  69. try:
  70. plugins = PluginService.list_installations_from_ids(tenant_id, args["plugin_ids"])
  71. except PluginDaemonClientSideError as e:
  72. raise ValueError(e)
  73. return jsonable_encoder({"plugins": plugins})
  74. class PluginIconApi(Resource):
  75. @setup_required
  76. def get(self):
  77. req = reqparse.RequestParser()
  78. req.add_argument("tenant_id", type=str, required=True, location="args")
  79. req.add_argument("filename", type=str, required=True, location="args")
  80. args = req.parse_args()
  81. try:
  82. icon_bytes, mimetype = PluginService.get_asset(args["tenant_id"], args["filename"])
  83. except PluginDaemonClientSideError as e:
  84. raise ValueError(e)
  85. icon_cache_max_age = dify_config.TOOL_ICON_CACHE_MAX_AGE
  86. return send_file(io.BytesIO(icon_bytes), mimetype=mimetype, max_age=icon_cache_max_age)
  87. class PluginUploadFromPkgApi(Resource):
  88. @setup_required
  89. @login_required
  90. @account_initialization_required
  91. @plugin_permission_required(install_required=True)
  92. def post(self):
  93. tenant_id = current_user.current_tenant_id
  94. file = request.files["pkg"]
  95. # check file size
  96. if file.content_length > dify_config.PLUGIN_MAX_PACKAGE_SIZE:
  97. raise ValueError("File size exceeds the maximum allowed size")
  98. content = file.read()
  99. try:
  100. response = PluginService.upload_pkg(tenant_id, content)
  101. except PluginDaemonClientSideError as e:
  102. raise ValueError(e)
  103. return jsonable_encoder(response)
  104. class PluginUploadFromGithubApi(Resource):
  105. @setup_required
  106. @login_required
  107. @account_initialization_required
  108. @plugin_permission_required(install_required=True)
  109. def post(self):
  110. tenant_id = current_user.current_tenant_id
  111. parser = reqparse.RequestParser()
  112. parser.add_argument("repo", type=str, required=True, location="json")
  113. parser.add_argument("version", type=str, required=True, location="json")
  114. parser.add_argument("package", type=str, required=True, location="json")
  115. args = parser.parse_args()
  116. try:
  117. response = PluginService.upload_pkg_from_github(tenant_id, args["repo"], args["version"], args["package"])
  118. except PluginDaemonClientSideError as e:
  119. raise ValueError(e)
  120. return jsonable_encoder(response)
  121. class PluginUploadFromBundleApi(Resource):
  122. @setup_required
  123. @login_required
  124. @account_initialization_required
  125. @plugin_permission_required(install_required=True)
  126. def post(self):
  127. tenant_id = current_user.current_tenant_id
  128. file = request.files["bundle"]
  129. # check file size
  130. if file.content_length > dify_config.PLUGIN_MAX_BUNDLE_SIZE:
  131. raise ValueError("File size exceeds the maximum allowed size")
  132. content = file.read()
  133. try:
  134. response = PluginService.upload_bundle(tenant_id, content)
  135. except PluginDaemonClientSideError as e:
  136. raise ValueError(e)
  137. return jsonable_encoder(response)
  138. class PluginInstallFromPkgApi(Resource):
  139. @setup_required
  140. @login_required
  141. @account_initialization_required
  142. @plugin_permission_required(install_required=True)
  143. def post(self):
  144. tenant_id = current_user.current_tenant_id
  145. parser = reqparse.RequestParser()
  146. parser.add_argument("plugin_unique_identifiers", type=list, required=True, location="json")
  147. args = parser.parse_args()
  148. # check if all plugin_unique_identifiers are valid string
  149. for plugin_unique_identifier in args["plugin_unique_identifiers"]:
  150. if not isinstance(plugin_unique_identifier, str):
  151. raise ValueError("Invalid plugin unique identifier")
  152. try:
  153. response = PluginService.install_from_local_pkg(tenant_id, args["plugin_unique_identifiers"])
  154. except PluginDaemonClientSideError as e:
  155. raise ValueError(e)
  156. return jsonable_encoder(response)
  157. class PluginInstallFromGithubApi(Resource):
  158. @setup_required
  159. @login_required
  160. @account_initialization_required
  161. @plugin_permission_required(install_required=True)
  162. def post(self):
  163. tenant_id = current_user.current_tenant_id
  164. parser = reqparse.RequestParser()
  165. parser.add_argument("repo", type=str, required=True, location="json")
  166. parser.add_argument("version", type=str, required=True, location="json")
  167. parser.add_argument("package", type=str, required=True, location="json")
  168. parser.add_argument("plugin_unique_identifier", type=str, required=True, location="json")
  169. args = parser.parse_args()
  170. try:
  171. response = PluginService.install_from_github(
  172. tenant_id,
  173. args["plugin_unique_identifier"],
  174. args["repo"],
  175. args["version"],
  176. args["package"],
  177. )
  178. except PluginDaemonClientSideError as e:
  179. raise ValueError(e)
  180. return jsonable_encoder(response)
  181. class PluginInstallFromMarketplaceApi(Resource):
  182. @setup_required
  183. @login_required
  184. @account_initialization_required
  185. @plugin_permission_required(install_required=True)
  186. def post(self):
  187. tenant_id = current_user.current_tenant_id
  188. parser = reqparse.RequestParser()
  189. parser.add_argument("plugin_unique_identifiers", type=list, required=True, location="json")
  190. args = parser.parse_args()
  191. # check if all plugin_unique_identifiers are valid string
  192. for plugin_unique_identifier in args["plugin_unique_identifiers"]:
  193. if not isinstance(plugin_unique_identifier, str):
  194. raise ValueError("Invalid plugin unique identifier")
  195. try:
  196. response = PluginService.install_from_marketplace_pkg(tenant_id, args["plugin_unique_identifiers"])
  197. except PluginDaemonClientSideError as e:
  198. raise ValueError(e)
  199. return jsonable_encoder(response)
  200. class PluginFetchMarketplacePkgApi(Resource):
  201. @setup_required
  202. @login_required
  203. @account_initialization_required
  204. @plugin_permission_required(install_required=True)
  205. def get(self):
  206. tenant_id = current_user.current_tenant_id
  207. parser = reqparse.RequestParser()
  208. parser.add_argument("plugin_unique_identifier", type=str, required=True, location="args")
  209. args = parser.parse_args()
  210. try:
  211. return jsonable_encoder(
  212. {
  213. "manifest": PluginService.fetch_marketplace_pkg(
  214. tenant_id,
  215. args["plugin_unique_identifier"],
  216. )
  217. }
  218. )
  219. except PluginDaemonClientSideError as e:
  220. raise ValueError(e)
  221. class PluginFetchManifestApi(Resource):
  222. @setup_required
  223. @login_required
  224. @account_initialization_required
  225. @plugin_permission_required(install_required=True)
  226. def get(self):
  227. tenant_id = current_user.current_tenant_id
  228. parser = reqparse.RequestParser()
  229. parser.add_argument("plugin_unique_identifier", type=str, required=True, location="args")
  230. args = parser.parse_args()
  231. try:
  232. return jsonable_encoder(
  233. {
  234. "manifest": PluginService.fetch_plugin_manifest(
  235. tenant_id, args["plugin_unique_identifier"]
  236. ).model_dump()
  237. }
  238. )
  239. except PluginDaemonClientSideError as e:
  240. raise ValueError(e)
  241. class PluginFetchInstallTasksApi(Resource):
  242. @setup_required
  243. @login_required
  244. @account_initialization_required
  245. @plugin_permission_required(install_required=True)
  246. def get(self):
  247. tenant_id = current_user.current_tenant_id
  248. parser = reqparse.RequestParser()
  249. parser.add_argument("page", type=int, required=True, location="args")
  250. parser.add_argument("page_size", type=int, required=True, location="args")
  251. args = parser.parse_args()
  252. try:
  253. return jsonable_encoder(
  254. {"tasks": PluginService.fetch_install_tasks(tenant_id, args["page"], args["page_size"])}
  255. )
  256. except PluginDaemonClientSideError as e:
  257. raise ValueError(e)
  258. class PluginFetchInstallTaskApi(Resource):
  259. @setup_required
  260. @login_required
  261. @account_initialization_required
  262. @plugin_permission_required(install_required=True)
  263. def get(self, task_id: str):
  264. tenant_id = current_user.current_tenant_id
  265. try:
  266. return jsonable_encoder({"task": PluginService.fetch_install_task(tenant_id, task_id)})
  267. except PluginDaemonClientSideError as e:
  268. raise ValueError(e)
  269. class PluginDeleteInstallTaskApi(Resource):
  270. @setup_required
  271. @login_required
  272. @account_initialization_required
  273. @plugin_permission_required(install_required=True)
  274. def post(self, task_id: str):
  275. tenant_id = current_user.current_tenant_id
  276. try:
  277. return {"success": PluginService.delete_install_task(tenant_id, task_id)}
  278. except PluginDaemonClientSideError as e:
  279. raise ValueError(e)
  280. class PluginDeleteAllInstallTaskItemsApi(Resource):
  281. @setup_required
  282. @login_required
  283. @account_initialization_required
  284. @plugin_permission_required(install_required=True)
  285. def post(self):
  286. tenant_id = current_user.current_tenant_id
  287. try:
  288. return {"success": PluginService.delete_all_install_task_items(tenant_id)}
  289. except PluginDaemonClientSideError as e:
  290. raise ValueError(e)
  291. class PluginDeleteInstallTaskItemApi(Resource):
  292. @setup_required
  293. @login_required
  294. @account_initialization_required
  295. @plugin_permission_required(install_required=True)
  296. def post(self, task_id: str, identifier: str):
  297. tenant_id = current_user.current_tenant_id
  298. try:
  299. return {"success": PluginService.delete_install_task_item(tenant_id, task_id, identifier)}
  300. except PluginDaemonClientSideError as e:
  301. raise ValueError(e)
  302. class PluginUpgradeFromMarketplaceApi(Resource):
  303. @setup_required
  304. @login_required
  305. @account_initialization_required
  306. @plugin_permission_required(install_required=True)
  307. def post(self):
  308. tenant_id = current_user.current_tenant_id
  309. parser = reqparse.RequestParser()
  310. parser.add_argument("original_plugin_unique_identifier", type=str, required=True, location="json")
  311. parser.add_argument("new_plugin_unique_identifier", type=str, required=True, location="json")
  312. args = parser.parse_args()
  313. try:
  314. return jsonable_encoder(
  315. PluginService.upgrade_plugin_with_marketplace(
  316. tenant_id, args["original_plugin_unique_identifier"], args["new_plugin_unique_identifier"]
  317. )
  318. )
  319. except PluginDaemonClientSideError as e:
  320. raise ValueError(e)
  321. class PluginUpgradeFromGithubApi(Resource):
  322. @setup_required
  323. @login_required
  324. @account_initialization_required
  325. @plugin_permission_required(install_required=True)
  326. def post(self):
  327. tenant_id = current_user.current_tenant_id
  328. parser = reqparse.RequestParser()
  329. parser.add_argument("original_plugin_unique_identifier", type=str, required=True, location="json")
  330. parser.add_argument("new_plugin_unique_identifier", type=str, required=True, location="json")
  331. parser.add_argument("repo", type=str, required=True, location="json")
  332. parser.add_argument("version", type=str, required=True, location="json")
  333. parser.add_argument("package", type=str, required=True, location="json")
  334. args = parser.parse_args()
  335. try:
  336. return jsonable_encoder(
  337. PluginService.upgrade_plugin_with_github(
  338. tenant_id,
  339. args["original_plugin_unique_identifier"],
  340. args["new_plugin_unique_identifier"],
  341. args["repo"],
  342. args["version"],
  343. args["package"],
  344. )
  345. )
  346. except PluginDaemonClientSideError as e:
  347. raise ValueError(e)
  348. class PluginUninstallApi(Resource):
  349. @setup_required
  350. @login_required
  351. @account_initialization_required
  352. @plugin_permission_required(install_required=True)
  353. def post(self):
  354. req = reqparse.RequestParser()
  355. req.add_argument("plugin_installation_id", type=str, required=True, location="json")
  356. args = req.parse_args()
  357. tenant_id = current_user.current_tenant_id
  358. try:
  359. return {"success": PluginService.uninstall(tenant_id, args["plugin_installation_id"])}
  360. except PluginDaemonClientSideError as e:
  361. raise ValueError(e)
  362. class PluginChangePermissionApi(Resource):
  363. @setup_required
  364. @login_required
  365. @account_initialization_required
  366. def post(self):
  367. user = current_user
  368. if not user.is_admin_or_owner:
  369. raise Forbidden()
  370. req = reqparse.RequestParser()
  371. req.add_argument("install_permission", type=str, required=True, location="json")
  372. req.add_argument("debug_permission", type=str, required=True, location="json")
  373. args = req.parse_args()
  374. install_permission = TenantPluginPermission.InstallPermission(args["install_permission"])
  375. debug_permission = TenantPluginPermission.DebugPermission(args["debug_permission"])
  376. tenant_id = user.current_tenant_id
  377. return {"success": PluginPermissionService.change_permission(tenant_id, install_permission, debug_permission)}
  378. class PluginFetchPermissionApi(Resource):
  379. @setup_required
  380. @login_required
  381. @account_initialization_required
  382. def get(self):
  383. tenant_id = current_user.current_tenant_id
  384. permission = PluginPermissionService.get_permission(tenant_id)
  385. if not permission:
  386. return jsonable_encoder(
  387. {
  388. "install_permission": TenantPluginPermission.InstallPermission.EVERYONE,
  389. "debug_permission": TenantPluginPermission.DebugPermission.EVERYONE,
  390. }
  391. )
  392. return jsonable_encoder(
  393. {
  394. "install_permission": permission.install_permission,
  395. "debug_permission": permission.debug_permission,
  396. }
  397. )
  398. class PluginFetchDynamicSelectOptionsApi(Resource):
  399. @setup_required
  400. @login_required
  401. @account_initialization_required
  402. def get(self):
  403. # check if the user is admin or owner
  404. if not current_user.is_admin_or_owner:
  405. raise Forbidden()
  406. tenant_id = current_user.current_tenant_id
  407. user_id = current_user.id
  408. parser = reqparse.RequestParser()
  409. parser.add_argument("plugin_id", type=str, required=True, location="args")
  410. parser.add_argument("provider", type=str, required=True, location="args")
  411. parser.add_argument("action", type=str, required=True, location="args")
  412. parser.add_argument("parameter", type=str, required=True, location="args")
  413. parser.add_argument("provider_type", type=str, required=True, location="args")
  414. args = parser.parse_args()
  415. try:
  416. options = PluginParameterService.get_dynamic_select_options(
  417. tenant_id,
  418. user_id,
  419. args["plugin_id"],
  420. args["provider"],
  421. args["action"],
  422. args["parameter"],
  423. args["provider_type"],
  424. )
  425. except PluginDaemonClientSideError as e:
  426. raise ValueError(e)
  427. return jsonable_encoder({"options": options})
  428. api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key")
  429. api.add_resource(PluginListApi, "/workspaces/current/plugin/list")
  430. api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions")
  431. api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids")
  432. api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon")
  433. api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg")
  434. api.add_resource(PluginUploadFromGithubApi, "/workspaces/current/plugin/upload/github")
  435. api.add_resource(PluginUploadFromBundleApi, "/workspaces/current/plugin/upload/bundle")
  436. api.add_resource(PluginInstallFromPkgApi, "/workspaces/current/plugin/install/pkg")
  437. api.add_resource(PluginInstallFromGithubApi, "/workspaces/current/plugin/install/github")
  438. api.add_resource(PluginUpgradeFromMarketplaceApi, "/workspaces/current/plugin/upgrade/marketplace")
  439. api.add_resource(PluginUpgradeFromGithubApi, "/workspaces/current/plugin/upgrade/github")
  440. api.add_resource(PluginInstallFromMarketplaceApi, "/workspaces/current/plugin/install/marketplace")
  441. api.add_resource(PluginFetchManifestApi, "/workspaces/current/plugin/fetch-manifest")
  442. api.add_resource(PluginFetchInstallTasksApi, "/workspaces/current/plugin/tasks")
  443. api.add_resource(PluginFetchInstallTaskApi, "/workspaces/current/plugin/tasks/<task_id>")
  444. api.add_resource(PluginDeleteInstallTaskApi, "/workspaces/current/plugin/tasks/<task_id>/delete")
  445. api.add_resource(PluginDeleteAllInstallTaskItemsApi, "/workspaces/current/plugin/tasks/delete_all")
  446. api.add_resource(PluginDeleteInstallTaskItemApi, "/workspaces/current/plugin/tasks/<task_id>/delete/<path:identifier>")
  447. api.add_resource(PluginUninstallApi, "/workspaces/current/plugin/uninstall")
  448. api.add_resource(PluginFetchMarketplacePkgApi, "/workspaces/current/plugin/marketplace/pkg")
  449. api.add_resource(PluginChangePermissionApi, "/workspaces/current/plugin/permission/change")
  450. api.add_resource(PluginFetchPermissionApi, "/workspaces/current/plugin/permission/fetch")
  451. api.add_resource(PluginFetchDynamicSelectOptionsApi, "/workspaces/current/plugin/parameters/dynamic-options")