diff --git a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx
index 957befb15a8..33acbde4813 100644
--- a/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/__tests__/agent-prompt-editor.spec.tsx
@@ -93,6 +93,33 @@ const mockBuiltInTools = vi.hoisted(() => [
},
])
+const wikipediaProvider = {
+ id: 'wikipedia',
+ name: 'Wikipedia',
+ author: 'Dify',
+ description: { en_US: 'Wikipedia tools' },
+ icon: 'wikipedia.svg',
+ icon_dark: 'wikipedia-dark.svg',
+ label: { en_US: 'Wikipedia' },
+ type: 'builtin',
+ team_credentials: {},
+ is_team_authorization: true,
+ allow_delete: false,
+ labels: [],
+ meta: {},
+ tools: [
+ {
+ name: 'wikipedia_search',
+ author: 'Dify',
+ label: { en_US: 'Wikipedia Search' },
+ description: { en_US: 'Search Wikipedia.' },
+ parameters: [],
+ labels: [],
+ output_schema: {},
+ },
+ ],
+}
+
vi.mock('@/app/components/base/prompt-editor', () => ({
__esModule: true,
default: (props: PromptEditorProps) => {
@@ -929,6 +956,60 @@ describe('AgentPromptEditor', () => {
])
})
+ it('should show configured providers and actions before other available tools', () => {
+ mockBuiltInTools.unshift(wikipediaProvider)
+ const configuredDuckDuckGoTranslateTool: AgentTool = {
+ ...duckDuckGoProviderTool,
+ actions: [
+ {
+ id: 'duckduckgo-translate',
+ name: 'DuckDuckGo Translate',
+ toolName: 'ddg_translate',
+ description: 'Translate search results.',
+ },
+ ],
+ }
+
+ const view = render(
+ ,
+ )
+
+ try {
+ const providerButtons = screen
+ .getAllByRole('button')
+ .filter(
+ (button) =>
+ button.textContent?.includes('DuckDuckGo') ||
+ button.textContent?.includes('Wikipedia'),
+ )
+
+ expect(providerButtons).toHaveLength(2)
+ expect(providerButtons[0]).toHaveTextContent('DuckDuckGo')
+ expect(providerButtons[1]).toHaveTextContent('Wikipedia')
+
+ fireEvent.click(screen.getByRole('button', { name: 'DuckDuckGo' }))
+ const actionButtons = screen.getAllByRole('button', {
+ name: /DuckDuckGo (Search|Translate)/,
+ })
+ expect(actionButtons[0]).toHaveTextContent('DuckDuckGo Translate')
+ expect(actionButtons[1]).toHaveTextContent('DuckDuckGo Search')
+ } finally {
+ view.unmount()
+ mockBuiltInTools.shift()
+ }
+ })
+
it('should close the slash menu when the trailing slash is deleted', async () => {
const { setPromptValue } = renderAgentPromptEditor('Review /')
diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx
index 48482025ceb..6767aa8ed9f 100644
--- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/slash.tsx
@@ -329,20 +329,23 @@ function AgentPromptToolRows({
const configuredCliTools = ENABLE_AGENT_CLI_TOOLS
? configuredTools.filter((tool) => tool.kind === 'cli')
: []
- const availableProviders = useMemo(() => {
- if (activeTab === 'all') return [...builtInTools, ...workflowTools, ...customTools, ...mcpTools]
- if (activeTab === ToolType.BuiltIn) return builtInTools
- if (activeTab === ToolType.Workflow) return workflowTools
- if (activeTab === ToolType.Custom) return customTools
- if (activeTab === ToolType.MCP) return mcpTools
-
- return []
- }, [activeTab, builtInTools, customTools, mcpTools, workflowTools])
-
const selectedTools = useMemo(
() => configuredTools.flatMap(toSelectedToolValue),
[configuredTools],
)
+ const availableProviders = useMemo(() => {
+ let providers: ToolWithProvider[] = []
+ if (activeTab === 'all')
+ providers = [...builtInTools, ...workflowTools, ...customTools, ...mcpTools]
+ if (activeTab === ToolType.BuiltIn) providers = builtInTools
+ if (activeTab === ToolType.Workflow) providers = workflowTools
+ if (activeTab === ToolType.Custom) providers = customTools
+ if (activeTab === ToolType.MCP) providers = mcpTools
+
+ return prioritizeItems(providers, (provider) =>
+ provider.tools.some((tool) => isToolSelected(selectedTools, provider, tool)),
+ )
+ }, [activeTab, builtInTools, customTools, mcpTools, selectedTools, workflowTools])
const tabs = [
{ key: 'all' as const, label: t(($) => $['agentDetail.configure.tools.toolTabs.all']) },
{
@@ -435,7 +438,9 @@ function AgentPromptToolRows({
onToggle={() => toggleProvider(provider.id)}
/>
{expandedProviderIds.has(provider.id) &&
- provider.tools.map((tool) => (
+ prioritizeItems(provider.tools, (tool) =>
+ isToolSelected(selectedTools, provider, tool),
+ ).map((tool) => (
(items: T[], isPriority: (item: T) => boolean) {
+ const priorityItems: T[] = []
+ const remainingItems: T[] = []
+
+ items.forEach((item) => {
+ if (isPriority(item)) priorityItems.push(item)
+ else remainingItems.push(item)
+ })
+
+ return [...priorityItems, ...remainingItems]
+}
+
function getLocalizedText(text: Record | undefined | null, language: string) {
if (!text) return ''