diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py
index deadf8e470e..2dede15330c 100644
--- a/api/controllers/console/workspace/plugin.py
+++ b/api/controllers/console/workspace/plugin.py
@@ -319,7 +319,7 @@ class PluginInstallationItemResponse(ResponseModel):
plugin_unique_identifier: str
version: str
checksum: str
- declaration: Mapping[str, Any]
+ declaration: PluginDeclarationResponse
class PluginInstallationsResponse(ResponseModel):
diff --git a/api/core/plugin/entities/marketplace.py b/api/core/plugin/entities/marketplace.py
index 03398873e30..0911adf2fd3 100644
--- a/api/core/plugin/entities/marketplace.py
+++ b/api/core/plugin/entities/marketplace.py
@@ -1,4 +1,4 @@
-from typing import Any
+from typing import Any, Literal
from pydantic import BaseModel, Field, computed_field, model_validator
@@ -32,7 +32,9 @@ class MarketplacePluginDeclaration(BaseModel):
latest_package_identifier: str = Field(
..., description="Unique identifier for the latest package release of the plugin"
)
- status: str = Field(..., description="Indicate the status of marketplace plugin, enum from `active` `deleted`")
+ status: Literal["active", "deleted"] = Field(
+ ..., description="Indicate the status of marketplace plugin, enum from `active` `deleted`"
+ )
deprecated_reason: str = Field(
..., description="Not empty when status='deleted', indicates the reason why this plugin is deleted(deprecated)"
)
diff --git a/api/core/plugin/plugin_service.py b/api/core/plugin/plugin_service.py
index 4b749bb4c90..b39032894e4 100644
--- a/api/core/plugin/plugin_service.py
+++ b/api/core/plugin/plugin_service.py
@@ -16,7 +16,7 @@ import logging
import time
from collections.abc import Mapping, Sequence
from mimetypes import guess_type
-from typing import Any, ClassVar
+from typing import Any, ClassVar, Literal
from pydantic import BaseModel, TypeAdapter, ValidationError
from redis import RedisError
@@ -75,7 +75,7 @@ class PluginService:
plugin_id: str
version: str
unique_identifier: str
- status: str
+ status: Literal["active", "deleted"]
deprecated_reason: str
alternative_plugin_id: str
diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md
index 3bd1bbed94f..3e9b93b086e 100644
--- a/api/openapi/markdown/console-openapi.md
+++ b/api/openapi/markdown/console-openapi.md
@@ -18093,7 +18093,7 @@ Enum class for large language model mode.
| alternative_plugin_id | string | | Yes |
| deprecated_reason | string | | Yes |
| plugin_id | string | | Yes |
-| status | string | | Yes |
+| status | string,
**Available values:** "active", "deleted" | *Enum:* `"active"`, `"deleted"` | Yes |
| unique_identifier | string | | Yes |
| version | string | | Yes |
@@ -19590,7 +19590,7 @@ Shared permission levels for resources (datasets, credentials, etc.)
| ---- | ---- | ----------- | -------- |
| checksum | string | | Yes |
| created_at | dateTime | | Yes |
-| declaration | object | | Yes |
+| declaration | [PluginDeclarationResponse](#plugindeclarationresponse) | | Yes |
| endpoints_active | integer | | Yes |
| endpoints_setups | integer | | Yes |
| id | string | | Yes |
diff --git a/api/tests/unit_tests/controllers/console/workspace/test_plugin.py b/api/tests/unit_tests/controllers/console/workspace/test_plugin.py
index bc76560dcac..2bd6be3bc7a 100644
--- a/api/tests/unit_tests/controllers/console/workspace/test_plugin.py
+++ b/api/tests/unit_tests/controllers/console/workspace/test_plugin.py
@@ -42,6 +42,7 @@ from controllers.console.workspace.plugin import (
PluginUploadFromGithubApi,
PluginUploadFromPkgApi,
)
+from core.plugin.entities.plugin import PluginInstallation
from core.plugin.impl.exc import PluginDaemonClientSideError
from models.account import Account, TenantAccountRole, TenantPluginAutoUpgradeStrategy, TenantPluginPermission
@@ -478,12 +479,23 @@ class TestPluginListInstallationsFromIdsApi:
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.plugin.PluginService.list_installations_from_ids",
- return_value=[{"id": "p1"}],
+ return_value=[PluginInstallation.model_validate(_plugin_category_list_item())],
),
):
result = method(api, "t1")
- assert "plugins" in result
+ assert result["plugins"][0]["id"] == "entity-1"
+ assert result["plugins"][0]["plugin_id"] == "test-author/test-plugin"
+ assert result["plugins"][0]["plugin_unique_identifier"] == "test-author/test-plugin:1.0.0@checksum"
+ assert result["plugins"][0]["version"] == "1.0.0"
+ assert result["plugins"][0]["declaration"]["name"] == "test-plugin"
+ assert "name" not in result["plugins"][0]
+ assert "installation_id" not in result["plugins"][0]
+ assert "latest_version" not in result["plugins"][0]
+ assert "latest_unique_identifier" not in result["plugins"][0]
+ assert "status" not in result["plugins"][0]
+ assert "deprecated_reason" not in result["plugins"][0]
+ assert "alternative_plugin_id" not in result["plugins"][0]
def test_daemon_error(self, app: Flask):
api = PluginListInstallationsFromIdsApi()
diff --git a/packages/contracts/generated/api/console/workspaces/types.gen.ts b/packages/contracts/generated/api/console/workspaces/types.gen.ts
index d4d7394ec94..5fd7557b763 100644
--- a/packages/contracts/generated/api/console/workspaces/types.gen.ts
+++ b/packages/contracts/generated/api/console/workspaces/types.gen.ts
@@ -1134,9 +1134,7 @@ export type PluginAutoUpgradeSettingsResponseModel = {
export type PluginInstallationItemResponse = {
checksum: string
created_at: string
- declaration: {
- [key: string]: unknown
- }
+ declaration: PluginDeclarationResponse
endpoints_active: number
endpoints_setups: number
id: string
@@ -1156,7 +1154,7 @@ export type LatestPluginCache = {
alternative_plugin_id: string
deprecated_reason: string
plugin_id: string
- status: string
+ status: 'active' | 'deleted'
unique_identifier: string
version: string
}
@@ -1500,39 +1498,6 @@ export type StrategySetting = 'disabled' | 'fix_only' | 'latest'
export type UpgradeMode = 'all' | 'exclude' | 'partial'
-export type PluginInstallationSource = 'github' | 'marketplace' | 'package' | 'remote'
-
-export type CoreToolsEntitiesCommonEntitiesI18nObject = {
- en_US: string
- ja_JP?: string | null
- pt_BR?: string | null
- zh_Hans?: string | null
-}
-
-export type PluginCategoryBuiltinToolResponse = {
- author: string
- description: CoreToolsEntitiesCommonEntitiesI18nObject
- label: CoreToolsEntitiesCommonEntitiesI18nObject
- labels: Array
- name: string
- output_schema: {
- [key: string]: unknown
- }
- parameters?: Array<{
- [key: string]: unknown
- }> | null
- [key: string]: unknown
-}
-
-export type ToolProviderType
- = | 'api'
- | 'app'
- | 'builtin'
- | 'dataset-retrieval'
- | 'mcp'
- | 'plugin'
- | 'workflow'
-
export type PluginDeclarationResponse = {
agent_strategy?: {
[key: string]: unknown
@@ -1573,6 +1538,39 @@ export type PluginDeclarationResponse = {
version: string
}
+export type PluginInstallationSource = 'github' | 'marketplace' | 'package' | 'remote'
+
+export type CoreToolsEntitiesCommonEntitiesI18nObject = {
+ en_US: string
+ ja_JP?: string | null
+ pt_BR?: string | null
+ zh_Hans?: string | null
+}
+
+export type PluginCategoryBuiltinToolResponse = {
+ author: string
+ description: CoreToolsEntitiesCommonEntitiesI18nObject
+ label: CoreToolsEntitiesCommonEntitiesI18nObject
+ labels: Array
+ name: string
+ output_schema: {
+ [key: string]: unknown
+ }
+ parameters?: Array<{
+ [key: string]: unknown
+ }> | null
+ [key: string]: unknown
+}
+
+export type ToolProviderType
+ = | 'api'
+ | 'app'
+ | 'builtin'
+ | 'dataset-retrieval'
+ | 'mcp'
+ | 'plugin'
+ | 'workflow'
+
export type RbacRoleAccount = {
account_id: string
account_name?: string
diff --git a/packages/contracts/generated/api/console/workspaces/zod.gen.ts b/packages/contracts/generated/api/console/workspaces/zod.gen.ts
index 6d0a1f0c404..e4f28cfacaa 100644
--- a/packages/contracts/generated/api/console/workspaces/zod.gen.ts
+++ b/packages/contracts/generated/api/console/workspaces/zod.gen.ts
@@ -1101,7 +1101,7 @@ export const zLatestPluginCache = z.object({
alternative_plugin_id: z.string(),
deprecated_reason: z.string(),
plugin_id: z.string(),
- status: z.string(),
+ status: z.enum(['active', 'deleted']),
unique_identifier: z.string(),
version: z.string(),
})
@@ -1716,33 +1716,6 @@ export const zPluginAutoUpgradeFetchResponse = z.object({
*/
export const zPluginInstallationSource = z.enum(['github', 'marketplace', 'package', 'remote'])
-/**
- * PluginInstallationItemResponse
- */
-export const zPluginInstallationItemResponse = z.object({
- checksum: z.string(),
- created_at: z.iso.datetime(),
- declaration: z.record(z.string(), z.unknown()),
- endpoints_active: z.int(),
- endpoints_setups: z.int(),
- id: z.string(),
- meta: z.record(z.string(), z.unknown()),
- plugin_id: z.string(),
- plugin_unique_identifier: z.string(),
- runtime_type: z.string(),
- source: zPluginInstallationSource,
- tenant_id: z.string(),
- updated_at: z.iso.datetime(),
- version: z.string(),
-})
-
-/**
- * PluginInstallationsResponse
- */
-export const zPluginInstallationsResponse = z.object({
- plugins: z.array(zPluginInstallationItemResponse),
-})
-
/**
* I18nObject
*
@@ -2348,6 +2321,33 @@ export const zPluginDeclarationResponse = z.object({
version: z.string(),
})
+/**
+ * PluginInstallationItemResponse
+ */
+export const zPluginInstallationItemResponse = z.object({
+ checksum: z.string(),
+ created_at: z.iso.datetime(),
+ declaration: zPluginDeclarationResponse,
+ endpoints_active: z.int(),
+ endpoints_setups: z.int(),
+ id: z.string(),
+ meta: z.record(z.string(), z.unknown()),
+ plugin_id: z.string(),
+ plugin_unique_identifier: z.string(),
+ runtime_type: z.string(),
+ source: zPluginInstallationSource,
+ tenant_id: z.string(),
+ updated_at: z.iso.datetime(),
+ version: z.string(),
+})
+
+/**
+ * PluginInstallationsResponse
+ */
+export const zPluginInstallationsResponse = z.object({
+ plugins: z.array(zPluginInstallationItemResponse),
+})
+
/**
* PluginCategoryInstalledPluginResponse
*/
diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx
index c71f79b042f..f7bf0d1ce05 100644
--- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.non-cloud.spec.tsx
@@ -84,6 +84,9 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
}))
vi.mock('@/service/use-plugins', () => ({
+ useCheckInstalled: () => ({
+ data: { plugins: [] },
+ }),
usePluginAutoUpgradeSettings: () => ({
data: {
category: 'model',
@@ -111,25 +114,40 @@ vi.mock('@/app/components/plugins/reference-setting-modal', () => ({
vi.mock('@/service/client', async (importOriginal) => {
const actual = await importOriginal()
- const originalPlugins = actual.consoleQuery.plugins as unknown as Record
+ const originalWorkspaces = actual.consoleQuery.workspaces
return {
...actual,
consoleQuery: new Proxy(actual.consoleQuery, {
get(target, prop) {
- if (prop === 'plugins') {
+ if (prop === 'workspaces') {
return {
- ...originalPlugins,
- checkInstalled: {
- queryOptions: () => ({
- queryKey: ['plugins', 'checkInstalled'],
- queryFn: () => new Promise(() => {}),
- }),
- },
- latestVersions: {
- queryOptions: () => ({
- queryKey: ['plugins', 'latestVersions'],
- queryFn: () => new Promise(() => {}),
- }),
+ ...originalWorkspaces,
+ current: {
+ ...originalWorkspaces.current,
+ plugin: {
+ ...originalWorkspaces.current.plugin,
+ list: {
+ ...originalWorkspaces.current.plugin.list,
+ installations: {
+ ids: {
+ post: {
+ queryOptions: () => ({
+ queryKey: ['workspaces', 'current', 'plugin', 'list', 'installations', 'ids', 'post'],
+ queryFn: () => new Promise(() => {}),
+ }),
+ },
+ },
+ },
+ latestVersions: {
+ post: {
+ queryOptions: () => ({
+ queryKey: ['workspaces', 'current', 'plugin', 'list', 'latestVersions', 'post'],
+ queryFn: () => new Promise(() => {}),
+ }),
+ },
+ },
+ },
+ },
},
}
}
diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx
index a6500b5e064..426276cfc5f 100644
--- a/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/__tests__/index.spec.tsx
@@ -163,6 +163,9 @@ vi.mock('@/service/use-plugins', () => ({
useInstalledPluginList: () => ({
data: { plugins: [] },
}),
+ useCheckInstalled: () => ({
+ data: { plugins: [] },
+ }),
usePluginAutoUpgradeSettings: () => ({
data: mockReferenceSetting.auto_upgrade
? {
@@ -230,25 +233,40 @@ vi.mock('@/app/components/base/date-and-time-picker/time-picker', () => ({
vi.mock('@/service/client', async (importOriginal) => {
const actual = await importOriginal()
- const originalPlugins = actual.consoleQuery.plugins as unknown as Record
+ const originalWorkspaces = actual.consoleQuery.workspaces
return {
...actual,
consoleQuery: new Proxy(actual.consoleQuery, {
get(target, prop) {
- if (prop === 'plugins') {
+ if (prop === 'workspaces') {
return {
- ...originalPlugins,
- checkInstalled: {
- queryOptions: () => ({
- queryKey: ['plugins', 'checkInstalled'],
- queryFn: () => new Promise(() => {}),
- }),
- },
- latestVersions: {
- queryOptions: () => ({
- queryKey: ['plugins', 'latestVersions'],
- queryFn: () => new Promise(() => {}),
- }),
+ ...originalWorkspaces,
+ current: {
+ ...originalWorkspaces.current,
+ plugin: {
+ ...originalWorkspaces.current.plugin,
+ list: {
+ ...originalWorkspaces.current.plugin.list,
+ installations: {
+ ids: {
+ post: {
+ queryOptions: () => ({
+ queryKey: ['workspaces', 'current', 'plugin', 'list', 'installations', 'ids', 'post'],
+ queryFn: () => new Promise(() => {}),
+ }),
+ },
+ },
+ },
+ latestVersions: {
+ post: {
+ queryOptions: () => ({
+ queryKey: ['workspaces', 'current', 'plugin', 'list', 'latestVersions', 'post'],
+ queryFn: () => new Promise(() => {}),
+ }),
+ },
+ },
+ },
+ },
},
}
}
diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx
index 24276bbb3db..5d8ddfd8f13 100644
--- a/web/app/components/header/account-setting/model-provider-page/index.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/index.tsx
@@ -3,7 +3,7 @@ import type {
ModelProvider,
} from './declarations'
import type { PluginDetail } from '@/app/components/plugins/types'
-import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
+import { useSuspenseQuery } from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
import { noop } from 'es-toolkit/function'
import { useMemo } from 'react'
@@ -14,7 +14,7 @@ import { usePluginSettingsAccess } from '@/app/components/plugins/plugin-page/us
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { useProviderContext } from '@/context/provider-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
-import { consoleQuery } from '@/service/client'
+import { useCheckInstalled } from '@/service/use-plugins'
import UpdateSettingDialog from '../update-setting-dialog'
import {
CustomConfigurationStatusEnum,
@@ -64,11 +64,10 @@ const ModelProviderPage = ({
const allPluginIds = useMemo(() => {
return [...new Set(providers.map(p => providerToPluginId(p.provider)).filter(Boolean))]
}, [providers])
- const { data: installedPlugins } = useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
- input: { body: { plugin_ids: allPluginIds } },
+ const { data: installedPlugins } = useCheckInstalled({
+ pluginIds: allPluginIds,
enabled: allPluginIds.length > 0,
- staleTime: 0,
- }))
+ })
const enrichedPlugins = usePluginsWithLatestVersion(installedPlugins?.plugins)
const pluginDetailMap = useMemo(() => {
const map = new Map()
diff --git a/web/app/components/plugins/__tests__/use-plugins-with-latest-version.spec.ts b/web/app/components/plugins/__tests__/use-plugins-with-latest-version.spec.ts
index 5e76afa218a..741fea0f07d 100644
--- a/web/app/components/plugins/__tests__/use-plugins-with-latest-version.spec.ts
+++ b/web/app/components/plugins/__tests__/use-plugins-with-latest-version.spec.ts
@@ -11,9 +11,17 @@ vi.mock('@tanstack/react-query', () => ({
vi.mock('@/service/client', () => ({
consoleQuery: {
- plugins: {
- latestVersions: {
- queryOptions: vi.fn((options: unknown) => options),
+ workspaces: {
+ current: {
+ plugin: {
+ list: {
+ latestVersions: {
+ post: {
+ queryOptions: vi.fn((options: unknown) => options),
+ },
+ },
+ },
+ },
},
},
},
@@ -55,7 +63,7 @@ describe('usePluginsWithLatestVersion', () => {
const { result } = renderHook(() => usePluginsWithLatestVersion(plugins))
- expect(consoleQuery.plugins.latestVersions.queryOptions).toHaveBeenCalledWith({
+ expect(consoleQuery.workspaces.current.plugin.list.latestVersions.post.queryOptions).toHaveBeenCalledWith({
input: { body: { plugin_ids: [] } },
enabled: false,
})
@@ -109,7 +117,7 @@ describe('usePluginsWithLatestVersion', () => {
const { result } = renderHook(() => usePluginsWithLatestVersion(plugins))
- expect(consoleQuery.plugins.latestVersions.queryOptions).toHaveBeenCalledWith({
+ expect(consoleQuery.workspaces.current.plugin.list.latestVersions.post.queryOptions).toHaveBeenCalledWith({
input: { body: { plugin_ids: ['plugin-1'] } },
enabled: true,
})
diff --git a/web/app/components/plugins/hooks.ts b/web/app/components/plugins/hooks.ts
index 5349cd4e852..28c5f17e809 100644
--- a/web/app/components/plugins/hooks.ts
+++ b/web/app/components/plugins/hooks.ts
@@ -109,7 +109,7 @@ export function usePluginsWithLatestVersion(plugins: PluginDetail[] = EMPTY_PLUG
[plugins],
)
- const { data: latestVersionData } = useQuery(consoleQuery.plugins.latestVersions.queryOptions({
+ const { data: latestVersionData } = useQuery(consoleQuery.workspaces.current.plugin.list.latestVersions.post.queryOptions({
input: { body: { plugin_ids: marketplacePluginIds } },
enabled: !!marketplacePluginIds.length,
}))
diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts
index 0d4863c5629..0e744ea3f46 100644
--- a/web/app/components/plugins/types.ts
+++ b/web/app/components/plugins/types.ts
@@ -450,18 +450,6 @@ export type InstalledPluginCategoryListResponse = {
has_more: boolean
}
-export type InstalledLatestVersionResponse = {
- versions: {
- [plugin_id: string]: {
- unique_identifier: string
- version: string
- status: 'active' | 'deleted'
- deprecated_reason: string
- alternative_plugin_id: string
- } | null
- }
-}
-
export type UninstallPluginResponse = {
success: boolean
}
diff --git a/web/contract/console/explore.ts b/web/contract/console/explore.ts
index 11c75397b9a..a1d8c980c99 100644
--- a/web/contract/console/explore.ts
+++ b/web/contract/console/explore.ts
@@ -158,3 +158,7 @@ export const exploreRouterContract = {
installedAppMeta: exploreInstalledAppMetaContract,
banners: exploreBannersContract,
}
+
+export const exploreConsoleRouterContract = {
+ explore: exploreRouterContract,
+}
diff --git a/web/contract/console/plugins.ts b/web/contract/console/plugins.ts
index 4269fd2acad..500f368bc34 100644
--- a/web/contract/console/plugins.ts
+++ b/web/contract/console/plugins.ts
@@ -1,32 +1 @@
-import type { InstalledLatestVersionResponse, PluginDetail } from '@/app/components/plugins/types'
-import { type } from '@orpc/contract'
-import { base } from '../base'
-
-export const pluginCheckInstalledContract = base
- .route({
- path: '/workspaces/current/plugin/list/installations/ids',
- method: 'POST',
- })
- .input(type<{
- body: {
- plugin_ids: string[]
- }
- }>())
- .output(type<{ plugins: PluginDetail[] }>())
-
-export const pluginLatestVersionsContract = base
- .route({
- path: '/workspaces/current/plugin/list/latest-versions',
- method: 'POST',
- })
- .input(type<{
- body: {
- plugin_ids: string[]
- }
- }>())
- .output(type())
-
-export const pluginsRouterContract = {
- checkInstalled: pluginCheckInstalledContract,
- latestVersions: pluginLatestVersionsContract,
-}
+export const pluginsConsoleRouterContract = {}
diff --git a/web/contract/console/snippets.ts b/web/contract/console/snippets.ts
index 0c9a9f7d9ec..768510dd240 100644
--- a/web/contract/console/snippets.ts
+++ b/web/contract/console/snippets.ts
@@ -370,3 +370,7 @@ export const snippetsRouterContract = {
runDraftWorkflow: runSnippetDraftWorkflowContract,
stopWorkflowTask: stopSnippetWorkflowTaskContract,
}
+
+export const snippetsConsoleRouterContract = {
+ snippets: snippetsRouterContract,
+}
diff --git a/web/contract/console/try-app.ts b/web/contract/console/try-app.ts
index eab05e7f135..76e01ca12c3 100644
--- a/web/contract/console/try-app.ts
+++ b/web/contract/console/try-app.ts
@@ -63,3 +63,7 @@ export const trialAppsRouterContract = {
parameters: trialAppParametersContract,
workflows: trialAppWorkflowsContract,
}
+
+export const trialAppsConsoleRouterContract = {
+ trialApps: trialAppsRouterContract,
+}
diff --git a/web/contract/router.ts b/web/contract/router.ts
index 45aea251f03..48ff9a2579b 100644
--- a/web/contract/router.ts
+++ b/web/contract/router.ts
@@ -45,10 +45,10 @@ import { workflowGenerate } from '@dify/contracts/api/console/workflow-generate/
import { workflow } from '@dify/contracts/api/console/workflow/orpc.gen'
import { workspaces } from '@dify/contracts/api/console/workspaces/orpc.gen'
import { contract as enterpriseContract } from '@dify/contracts/enterprise/orpc.gen'
-import { exploreRouterContract } from './console/explore'
-import { pluginsRouterContract } from './console/plugins'
-import { snippetsRouterContract } from './console/snippets'
-import { trialAppsRouterContract } from './console/try-app'
+import { exploreConsoleRouterContract } from './console/explore'
+import { pluginsConsoleRouterContract } from './console/plugins'
+import { snippetsConsoleRouterContract } from './console/snippets'
+import { trialAppsConsoleRouterContract } from './console/try-app'
const communityContract = {
account,
@@ -102,8 +102,8 @@ const communityContract = {
export const consoleRouterContract = {
enterprise: enterpriseContract,
...communityContract,
- explore: exploreRouterContract,
- plugins: pluginsRouterContract,
- snippets: snippetsRouterContract,
- trialApps: trialAppsRouterContract,
+ ...exploreConsoleRouterContract,
+ ...pluginsConsoleRouterContract,
+ ...snippetsConsoleRouterContract,
+ ...trialAppsConsoleRouterContract,
}
diff --git a/web/service/__tests__/use-plugins.spec.tsx b/web/service/__tests__/use-plugins.spec.tsx
index 015614c4a50..6d8374591a6 100644
--- a/web/service/__tests__/use-plugins.spec.tsx
+++ b/web/service/__tests__/use-plugins.spec.tsx
@@ -1,11 +1,14 @@
+import type { PluginInstallationItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type { ReactNode } from 'react'
import type { Permissions, PluginTaskStart } from '@/app/components/plugins/types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { FormTypeEnum } from '@/app/components/base/form/types'
import { AUTO_UPDATE_MODE, AUTO_UPDATE_STRATEGY } from '@/app/components/plugins/reference-setting-modal/auto-update-setting/types'
import { PermissionType, PluginCategoryEnum, PluginSource, TaskStatus } from '@/app/components/plugins/types'
import {
+ normalizeInstalledPluginDetail,
useInstalledPluginList,
useMutationPluginAutoUpgradeSettings,
useMutationPluginPermissionSettings,
@@ -67,6 +70,177 @@ const createWrapper = (queryClient: QueryClient) => {
}
}
+const createPluginInstallation = (): PluginInstallationItemResponse => ({
+ id: 'installation-row-id',
+ created_at: '2026-06-01T00:00:00Z',
+ updated_at: '2026-06-02T00:00:00Z',
+ plugin_id: 'langgenius/full-declaration',
+ plugin_unique_identifier: 'langgenius/full-declaration:1.0.0@test',
+ tenant_id: 'tenant-id',
+ endpoints_setups: 1,
+ endpoints_active: 1,
+ version: '1.0.0',
+ source: PluginSource.marketplace,
+ runtime_type: 'local',
+ checksum: 'checksum',
+ meta: {
+ repo: 'langgenius/full-declaration',
+ version: '1.0.0',
+ package: 'full-declaration.difypkg',
+ },
+ declaration: {
+ version: '1.0.0',
+ author: 'Dify',
+ icon: 'icon.svg',
+ icon_dark: 'icon-dark.svg',
+ name: 'full-declaration',
+ category: PluginCategoryEnum.trigger,
+ label: { en_US: 'Full declaration', zh_Hans: 'Full declaration' },
+ description: { en_US: 'Full declaration plugin', zh_Hans: 'Full declaration plugin' },
+ created_at: '2026-06-01T00:00:00Z',
+ resource: {},
+ plugins: {},
+ verified: true,
+ tags: ['automation'],
+ meta: {
+ version: '1.0.0',
+ minimum_dify_version: '1.4.0',
+ },
+ tool: {
+ identity: {
+ author: 'Dify',
+ name: 'tool-provider',
+ description: { en_US: 'Tool provider' },
+ icon: 'tool.svg',
+ label: { en_US: 'Tool provider' },
+ tags: ['tool'],
+ },
+ credentials_schema: [
+ {
+ name: 'api_key',
+ label: { en_US: 'API key' },
+ type: 'secret-input',
+ required: true,
+ default: 'token',
+ },
+ ],
+ },
+ datasource: {
+ identity: {
+ author: 'Dify',
+ name: 'datasource-provider',
+ description: { en_US: 'Datasource provider' },
+ icon: 'datasource.svg',
+ label: { en_US: 'Datasource provider' },
+ tags: ['datasource'],
+ },
+ credentials_schema: [],
+ },
+ endpoint: {
+ settings: [
+ {
+ name: 'endpoint_secret',
+ label: { en_US: 'Endpoint secret' },
+ type: 'secret-input',
+ required: true,
+ default: '',
+ },
+ ],
+ endpoints: [
+ {
+ path: '/webhook',
+ method: 'POST',
+ hidden: false,
+ },
+ ],
+ },
+ trigger: {
+ events: [
+ {
+ name: 'issue_created',
+ identity: {
+ author: 'Dify',
+ name: 'issue_created',
+ label: { en_US: 'Issue created' },
+ provider: 'github',
+ },
+ description: { en_US: 'Issue created event' },
+ parameters: [
+ {
+ name: 'retry_count',
+ label: { en_US: 'Retry count' },
+ type: 'number',
+ default: 0,
+ required: false,
+ multiple: false,
+ },
+ ],
+ output_schema: {
+ type: 'object',
+ },
+ },
+ ],
+ identity: {
+ author: 'Dify',
+ name: 'github-trigger',
+ label: { en_US: 'GitHub trigger' },
+ description: { en_US: 'GitHub trigger provider' },
+ icon: 'trigger.svg',
+ tags: ['trigger'],
+ },
+ subscription_constructor: {
+ credentials_schema: [],
+ oauth_schema: {
+ client_schema: [],
+ credentials_schema: [],
+ },
+ parameters: [
+ {
+ name: 'max_retries',
+ label: { en_US: 'Max retries' },
+ type: 'number',
+ default: 3,
+ required: false,
+ multiple: false,
+ },
+ ],
+ },
+ subscription_schema: [
+ {
+ name: 'repository',
+ label: { en_US: 'Repository' },
+ type: 'text-input',
+ required: true,
+ },
+ ],
+ },
+ },
+})
+
+describe('normalizeInstalledPluginDetail', () => {
+ it('should preserve generated plugin declaration capabilities', () => {
+ const detail = normalizeInstalledPluginDetail(createPluginInstallation())
+
+ expect(detail.name).toBe('full-declaration')
+ expect(detail.installation_id).toBe('installation-row-id')
+ expect(detail.latest_version).toBe('1.0.0')
+ expect(detail.latest_unique_identifier).toBe('langgenius/full-declaration:1.0.0@test')
+ expect(detail.status).toBe('active')
+ expect(detail.declaration.tool?.identity.name).toBe('tool-provider')
+ expect(detail.declaration.tool?.credentials_schema[0]?.name).toBe('api_key')
+ expect(detail.declaration.datasource?.identity.name).toBe('datasource-provider')
+ expect(detail.declaration.endpoint?.endpoints?.[0]).toEqual({
+ path: '/webhook',
+ method: 'POST',
+ hidden: false,
+ })
+ expect(detail.declaration.trigger.identity.name).toBe('github-trigger')
+ expect(detail.declaration.trigger.events[0]?.parameters[0]?.default).toBe(0)
+ expect(detail.declaration.trigger.subscription_constructor.parameters[0]?.type).toBe(FormTypeEnum.textNumber)
+ expect(detail.declaration.trigger.subscription_schema[0]?.name).toBe('repository')
+ })
+})
+
describe('use-plugins mutations', () => {
beforeEach(() => {
vi.clearAllMocks()
diff --git a/web/service/console-router-loader.ts b/web/service/console-router-loader.ts
index ec01453dfa8..60ce244620c 100644
--- a/web/service/console-router-loader.ts
+++ b/web/service/console-router-loader.ts
@@ -1,7 +1,9 @@
import type { AnyContractRouter } from '@orpc/contract'
import { contractLoaders } from '@dify/contracts/api/console/orpc.gen'
-const wrapConsoleContract = (segment: string, contract: unknown) => ({ [segment]: contract }) as AnyContractRouter
+type ConsoleContractExtension = Record
+
+const wrapConsoleContract = (segment: string, contract: unknown): ConsoleContractExtension => ({ [segment]: contract })
async function loadGeneratedConsoleContract(segment: string) {
const loader = contractLoaders[segment as keyof typeof contractLoaders]
@@ -11,18 +13,21 @@ async function loadGeneratedConsoleContract(segment: string) {
return loader() as Promise
}
-const customConsoleContractLoaders: Record Promise> = {
+const customConsoleContractLoaders: Record Promise> = {
enterprise: () => import('@dify/contracts/enterprise/orpc.gen').then(({ contract }) => wrapConsoleContract('enterprise', contract)),
- explore: () => import('@/contract/console/explore').then(({ exploreRouterContract }) => wrapConsoleContract('explore', exploreRouterContract)),
- plugins: () => import('@/contract/console/plugins').then(({ pluginsRouterContract }) => wrapConsoleContract('plugins', pluginsRouterContract)),
- snippets: () => import('@/contract/console/snippets').then(({ snippetsRouterContract }) => wrapConsoleContract('snippets', snippetsRouterContract)),
- trialApps: () => import('@/contract/console/try-app').then(({ trialAppsRouterContract }) => wrapConsoleContract('trialApps', trialAppsRouterContract)),
+ explore: () => import('@/contract/console/explore').then(({ exploreConsoleRouterContract }) => exploreConsoleRouterContract),
+ plugins: () => import('@/contract/console/plugins').then(({ pluginsConsoleRouterContract }) => pluginsConsoleRouterContract),
+ snippets: () => import('@/contract/console/snippets').then(({ snippetsConsoleRouterContract }) => snippetsConsoleRouterContract),
+ trialApps: () => import('@/contract/console/try-app').then(({ trialAppsConsoleRouterContract }) => trialAppsConsoleRouterContract),
}
export async function loadConsoleContractForSegment(segment: string) {
const customContractLoader = customConsoleContractLoaders[segment]
- if (customContractLoader)
- return customContractLoader()
+ if (customContractLoader) {
+ const customContract = await customContractLoader()
+ if (Object.keys(customContract).length > 0)
+ return customContract as AnyContractRouter
+ }
const generatedContract = await loadGeneratedConsoleContract(segment)
if (generatedContract)
diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts
index 38b36c89c11..b13fd0f4241 100644
--- a/web/service/use-plugins.ts
+++ b/web/service/use-plugins.ts
@@ -1,3 +1,4 @@
+import type { PluginInstallationItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type { MutateOptions, QueryClient, QueryOptions } from '@tanstack/react-query'
import type {
FormOption,
@@ -14,10 +15,12 @@ import type {
InstalledPluginListWithTotalResponse,
InstallPackageResponse,
InstallStatusResponse,
+ MetaData,
PackageDependency,
Permissions,
Plugin,
PluginDeclaration,
+ PluginDetail,
PluginInfoFromMarketPlace,
PluginsFromMarketplaceByInfoResponse,
PluginsFromMarketplaceResponse,
@@ -36,9 +39,10 @@ import {
} from '@tanstack/react-query'
import { cloneDeep } from 'es-toolkit/object'
import { useCallback, useEffect, useRef } from 'react'
+import { FormTypeEnum } from '@/app/components/base/form/types'
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
-import { PluginCategoryEnum, TaskStatus } from '@/app/components/plugins/types'
+import { PluginCategoryEnum, PluginSource, TaskStatus } from '@/app/components/plugins/types'
import { useAppContext } from '@/context/app-context'
import { fetchModelProviderModelList } from '@/service/common'
import { fetchPluginInfoFromMarketPlace, uninstallPlugin } from '@/service/plugins'
@@ -56,6 +60,417 @@ type PluginTaskListResponse = {
tasks: PluginTask[]
}
+const getString = (value: unknown) => {
+ return typeof value === 'string' ? value : ''
+}
+
+const getBoolean = (value: unknown) => {
+ return value === true
+}
+
+const getNumber = (value: unknown) => {
+ return typeof value === 'number' ? value : undefined
+}
+
+const isRecord = (value: unknown): value is Record => {
+ return !!value && typeof value === 'object' && !Array.isArray(value)
+}
+
+const getRecord = (value: unknown, key: string) => {
+ if (!isRecord(value))
+ return undefined
+
+ const child = value[key]
+ return isRecord(child) ? child : undefined
+}
+
+const getRecordArray = (value: unknown, key: string) => {
+ if (!isRecord(value))
+ return []
+
+ const child = value[key]
+ return Array.isArray(child) ? child.filter(isRecord) : []
+}
+
+const getStringArray = (value: unknown) => {
+ return Array.isArray(value) ? value.filter(item => typeof item === 'string') : []
+}
+
+const getI18nValue = (value: object | null | undefined, key: string) => {
+ return value ? getString(Object.entries(value).find(([itemKey]) => itemKey === key)?.[1]) : ''
+}
+
+const normalizeI18nObject = (value: object | null | undefined, fallback = ''): PluginDeclaration['label'] => {
+ const en = getI18nValue(value, 'en_US') || getI18nValue(value, 'en-US') || fallback
+ const zhHans = getI18nValue(value, 'zh_Hans') || getI18nValue(value, 'zh-Hans') || en
+ const ja = getI18nValue(value, 'ja_JP') || getI18nValue(value, 'ja-JP') || en
+ const ptBr = getI18nValue(value, 'pt_BR') || getI18nValue(value, 'pt-BR') || en
+
+ return {
+ 'en-US': en,
+ 'zh-Hans': zhHans,
+ 'zh-Hant': en,
+ 'pt-BR': ptBr,
+ 'es-ES': en,
+ 'fr-FR': en,
+ 'de-DE': en,
+ 'ja-JP': ja,
+ 'ko-KR': en,
+ 'ru-RU': en,
+ 'it-IT': en,
+ 'th-TH': en,
+ 'uk-UA': en,
+ 'vi-VN': en,
+ 'ro-RO': en,
+ 'pl-PL': en,
+ 'hi-IN': en,
+ 'tr-TR': en,
+ 'fa-IR': en,
+ 'sl-SI': en,
+ 'id-ID': en,
+ 'nl-NL': en,
+ 'ar-TN': en,
+ 'en_US': en,
+ 'zh_Hans': zhHans,
+ 'ja_JP': ja,
+ }
+}
+
+const normalizePluginCategory = (category: PluginInstallationItemResponse['declaration']['category']): PluginCategoryEnum => {
+ switch (category) {
+ case PluginCategoryEnum.tool:
+ return PluginCategoryEnum.tool
+ case PluginCategoryEnum.model:
+ return PluginCategoryEnum.model
+ case PluginCategoryEnum.datasource:
+ return PluginCategoryEnum.datasource
+ case PluginCategoryEnum.trigger:
+ return PluginCategoryEnum.trigger
+ case PluginCategoryEnum.agent:
+ return PluginCategoryEnum.agent
+ case PluginCategoryEnum.extension:
+ return PluginCategoryEnum.extension
+ }
+ return PluginCategoryEnum.extension
+}
+
+const normalizePluginSource = (source: PluginInstallationItemResponse['source']): PluginSource => {
+ switch (source) {
+ case PluginSource.github:
+ return PluginSource.github
+ case PluginSource.marketplace:
+ return PluginSource.marketplace
+ case PluginSource.local:
+ return PluginSource.local
+ case PluginSource.debugging:
+ return PluginSource.debugging
+ }
+ return PluginSource.marketplace
+}
+
+const normalizePluginMeta = (meta: Record): MetaData => {
+ return {
+ repo: getString(meta.repo),
+ version: getString(meta.version),
+ package: getString(meta.package),
+ }
+}
+
+const normalizeToolCredentialOption = (
+ option: Record,
+): NonNullable['credentials_schema'][number]['options']>[number] => ({
+ label: normalizeI18nObject(getRecord(option, 'label'), getString(option.value)),
+ value: getString(option.value),
+})
+
+const normalizeToolCredential = (
+ credential: Record,
+): NonNullable['credentials_schema'][number] => ({
+ name: getString(credential.name),
+ label: normalizeI18nObject(getRecord(credential, 'label'), getString(credential.name)),
+ help: credential.help === null ? null : normalizeI18nObject(getRecord(credential, 'help')),
+ placeholder: normalizeI18nObject(getRecord(credential, 'placeholder')),
+ type: getString(credential.type),
+ required: getBoolean(credential.required),
+ default: getString(credential.default),
+ options: getRecordArray(credential, 'options').map(normalizeToolCredentialOption),
+})
+
+const normalizePluginToolDeclaration = (value: unknown): PluginDeclaration['tool'] => {
+ if (!isRecord(value))
+ return undefined
+
+ const identity = getRecord(value, 'identity')
+ if (!identity)
+ return undefined
+
+ const name = getString(identity.name)
+
+ return {
+ identity: {
+ author: getString(identity.author),
+ name,
+ description: normalizeI18nObject(getRecord(identity, 'description')),
+ icon: getString(identity.icon),
+ label: normalizeI18nObject(getRecord(identity, 'label'), name),
+ tags: getStringArray(identity.tags),
+ },
+ credentials_schema: getRecordArray(value, 'credentials_schema').map(normalizeToolCredential),
+ }
+}
+
+const normalizePluginEndpointDeclaration = (value: unknown): PluginDeclaration['endpoint'] => {
+ if (!isRecord(value))
+ return undefined
+
+ return {
+ settings: getRecordArray(value, 'settings').map(normalizeToolCredential),
+ endpoints: getRecordArray(value, 'endpoints').map(endpoint => ({
+ path: getString(endpoint.path),
+ method: getString(endpoint.method),
+ hidden: endpoint.hidden === undefined ? undefined : getBoolean(endpoint.hidden),
+ })),
+ }
+}
+
+const normalizeParameterDefault = (value: unknown) => {
+ if (Array.isArray(value))
+ return value.filter(item => typeof item === 'string')
+ if (typeof value === 'string')
+ return value
+ if (value === undefined || value === null)
+ return undefined
+ return String(value)
+}
+
+const normalizeFormType = (type: unknown): FormTypeEnum => {
+ switch (type) {
+ case FormTypeEnum.appSelector:
+ return FormTypeEnum.appSelector
+ case FormTypeEnum.boolean:
+ return FormTypeEnum.boolean
+ case FormTypeEnum.checkbox:
+ return FormTypeEnum.checkbox
+ case FormTypeEnum.dynamicSelect:
+ return FormTypeEnum.dynamicSelect
+ case FormTypeEnum.file:
+ return FormTypeEnum.file
+ case FormTypeEnum.files:
+ return FormTypeEnum.files
+ case FormTypeEnum.modelSelector:
+ return FormTypeEnum.modelSelector
+ case FormTypeEnum.multiToolSelector:
+ return FormTypeEnum.multiToolSelector
+ case FormTypeEnum.radio:
+ return FormTypeEnum.radio
+ case FormTypeEnum.secretInput:
+ return FormTypeEnum.secretInput
+ case FormTypeEnum.select:
+ return FormTypeEnum.select
+ case FormTypeEnum.textNumber:
+ case 'number':
+ return FormTypeEnum.textNumber
+ case FormTypeEnum.textInput:
+ case 'string':
+ case 'array':
+ case 'object':
+ return FormTypeEnum.textInput
+ }
+ return FormTypeEnum.textInput
+}
+
+const normalizeParameterOption = (
+ option: Record,
+): NonNullable[number] => ({
+ value: getString(option.value),
+ label: normalizeI18nObject(getRecord(option, 'label'), getString(option.value)),
+ icon: getString(option.icon) || undefined,
+})
+
+const normalizeParameterSchema = (
+ parameter: Record,
+): PluginDeclaration['trigger']['subscription_schema'][number] => ({
+ name: getString(parameter.name),
+ label: normalizeI18nObject(getRecord(parameter, 'label'), getString(parameter.name)),
+ type: normalizeFormType(parameter.type),
+ auto_generate: parameter.auto_generate ?? undefined,
+ template: parameter.template ?? undefined,
+ scope: parameter.scope ?? undefined,
+ required: getBoolean(parameter.required),
+ multiple: getBoolean(parameter.multiple),
+ default: normalizeParameterDefault(parameter.default),
+ min: parameter.min ?? undefined,
+ max: parameter.max ?? undefined,
+ precision: parameter.precision ?? undefined,
+ options: getRecordArray(parameter, 'options').map(normalizeParameterOption),
+ description: normalizeI18nObject(getRecord(parameter, 'description')),
+})
+
+const normalizeCredentialSchema = (
+ schema: Record,
+): PluginDeclaration['trigger']['subscription_constructor']['credentials_schema'][number] => ({
+ name: getString(schema.name),
+ label: normalizeI18nObject(getRecord(schema, 'label'), getString(schema.name)),
+ description: normalizeI18nObject(getRecord(schema, 'description')),
+ type: normalizeFormType(schema.type),
+ scope: schema.scope ?? undefined,
+ required: getBoolean(schema.required),
+ default: schema.default ?? undefined,
+ options: getRecordArray(schema, 'options').map(normalizeParameterOption),
+ help: normalizeI18nObject(getRecord(schema, 'help')),
+ url: getString(schema.url),
+ placeholder: normalizeI18nObject(getRecord(schema, 'placeholder')),
+})
+
+const normalizeTriggerEventParameter = (
+ parameter: Record,
+): PluginDeclaration['trigger']['events'][number]['parameters'][number] => ({
+ name: getString(parameter.name),
+ label: normalizeI18nObject(getRecord(parameter, 'label'), getString(parameter.name)),
+ type: getString(parameter.type),
+ auto_generate: parameter.auto_generate ?? undefined,
+ template: parameter.template ?? undefined,
+ scope: parameter.scope ?? undefined,
+ required: getBoolean(parameter.required),
+ multiple: getBoolean(parameter.multiple),
+ default: parameter.default ?? '',
+ min: getNumber(parameter.min),
+ max: getNumber(parameter.max),
+ precision: getNumber(parameter.precision),
+ options: getRecordArray(parameter, 'options').map(normalizeParameterOption),
+ description: normalizeI18nObject(getRecord(parameter, 'description')),
+})
+
+const normalizeTriggerEvent = (
+ event: Record,
+): PluginDeclaration['trigger']['events'][number] => {
+ const identity = getRecord(event, 'identity')
+ const name = getString(event.name)
+
+ return {
+ name,
+ identity: {
+ author: getString(identity?.author),
+ name: getString(identity?.name) || name,
+ label: normalizeI18nObject(getRecord(identity, 'label'), name),
+ provider: getString(identity?.provider) || undefined,
+ },
+ description: normalizeI18nObject(getRecord(event, 'description'), name),
+ parameters: getRecordArray(event, 'parameters').map(normalizeTriggerEventParameter),
+ output_schema: getRecord(event, 'output_schema') ?? {},
+ }
+}
+
+const createEmptyTrigger = (name: string): PluginDeclaration['trigger'] => ({
+ events: [],
+ identity: {
+ author: '',
+ name,
+ label: normalizeI18nObject(undefined, name),
+ description: normalizeI18nObject(undefined),
+ icon: '',
+ tags: [],
+ },
+ subscription_constructor: {
+ credentials_schema: [],
+ oauth_schema: {
+ client_schema: [],
+ credentials_schema: [],
+ },
+ parameters: [],
+ },
+ subscription_schema: [],
+})
+
+const normalizePluginTriggerDeclaration = (
+ value: unknown,
+ fallbackName: string,
+): PluginDeclaration['trigger'] => {
+ if (!isRecord(value))
+ return createEmptyTrigger(fallbackName)
+
+ const identity = getRecord(value, 'identity')
+ const subscriptionConstructor = getRecord(value, 'subscription_constructor')
+ const oauthSchema = getRecord(subscriptionConstructor, 'oauth_schema')
+
+ return {
+ events: getRecordArray(value, 'events').map(normalizeTriggerEvent),
+ identity: {
+ author: getString(identity?.author),
+ name: getString(identity?.name) || fallbackName,
+ label: normalizeI18nObject(getRecord(identity, 'label'), fallbackName),
+ description: normalizeI18nObject(getRecord(identity, 'description')),
+ icon: getString(identity?.icon),
+ tags: getStringArray(identity?.tags),
+ },
+ subscription_constructor: {
+ credentials_schema: getRecordArray(subscriptionConstructor, 'credentials_schema').map(normalizeCredentialSchema),
+ oauth_schema: {
+ client_schema: getRecordArray(oauthSchema, 'client_schema').map(normalizeCredentialSchema),
+ credentials_schema: getRecordArray(oauthSchema, 'credentials_schema').map(normalizeCredentialSchema),
+ },
+ parameters: getRecordArray(subscriptionConstructor, 'parameters').map(normalizeParameterSchema),
+ },
+ subscription_schema: getRecordArray(value, 'subscription_schema').map(normalizeParameterSchema),
+ }
+}
+
+const normalizePluginDeclaration = (plugin: PluginInstallationItemResponse): PluginDeclaration => {
+ const { declaration } = plugin
+ return {
+ plugin_unique_identifier: plugin.plugin_unique_identifier,
+ version: declaration.version,
+ author: declaration.author ?? '',
+ icon: declaration.icon,
+ icon_dark: declaration.icon_dark ?? undefined,
+ name: declaration.name,
+ category: normalizePluginCategory(declaration.category),
+ label: normalizeI18nObject(declaration.label, declaration.name),
+ description: normalizeI18nObject(declaration.description, declaration.name),
+ created_at: declaration.created_at,
+ resource: declaration.resource,
+ plugins: declaration.plugins,
+ verified: declaration.verified ?? false,
+ endpoint: normalizePluginEndpointDeclaration(declaration.endpoint),
+ tool: normalizePluginToolDeclaration(declaration.tool),
+ datasource: normalizePluginToolDeclaration(declaration.datasource),
+ model: declaration.model,
+ tags: declaration.tags ?? [],
+ agent_strategy: declaration.agent_strategy,
+ meta: {
+ version: getString(declaration.meta.version) || declaration.version,
+ minimum_dify_version: getString(declaration.meta.minimum_dify_version) || undefined,
+ },
+ trigger: normalizePluginTriggerDeclaration(declaration.trigger, declaration.name),
+ }
+}
+
+export const normalizeInstalledPluginDetail = (plugin: PluginInstallationItemResponse): PluginDetail => {
+ const declaration = normalizePluginDeclaration(plugin)
+
+ return {
+ id: plugin.id,
+ created_at: plugin.created_at,
+ updated_at: plugin.updated_at,
+ name: declaration.name,
+ plugin_id: plugin.plugin_id,
+ plugin_unique_identifier: plugin.plugin_unique_identifier,
+ declaration,
+ installation_id: plugin.id,
+ tenant_id: plugin.tenant_id,
+ endpoints_setups: plugin.endpoints_setups,
+ endpoints_active: plugin.endpoints_active,
+ version: plugin.version,
+ latest_version: plugin.version,
+ latest_unique_identifier: plugin.plugin_unique_identifier,
+ source: normalizePluginSource(plugin.source),
+ meta: normalizePluginMeta(plugin.meta),
+ status: 'active',
+ deprecated_reason: '',
+ alternative_plugin_id: '',
+ }
+}
+
const isUnfinishedPluginTask = (task: PluginTask) => task.status === TaskStatus.pending || task.status === TaskStatus.running
const normalizeStartedPluginTask = (task: PluginTaskStart): PluginTask => ({
@@ -114,10 +529,13 @@ export const useCheckInstalled = ({
pluginIds: string[]
enabled: boolean
}) => {
- return useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
+ return useQuery(consoleQuery.workspaces.current.plugin.list.installations.ids.post.queryOptions({
input: { body: { plugin_ids: pluginIds } },
enabled,
staleTime: 0,
+ select: response => ({
+ plugins: response.plugins.map(normalizeInstalledPluginDetail),
+ }),
}))
}
@@ -125,7 +543,7 @@ export const useInvalidateCheckInstalled = () => {
const queryClient = useQueryClient()
return () => {
queryClient.invalidateQueries({
- queryKey: consoleQuery.plugins.checkInstalled.key(),
+ queryKey: consoleQuery.workspaces.current.plugin.list.installations.ids.post.key(),
})
}
}