mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
chore: new agent enchance (#39040)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -176,6 +176,10 @@ describe('isAgentSuggestedModel', () => {
|
||||
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('gpt-5.5'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('gpt-5.5-pro'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('gpt-5.6'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('gpt-5.6-sol'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('gpt-5.6-terra'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('gpt-5.6-luna'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('Claude Opus 4.8'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('opus-4.7'))).toBe(true)
|
||||
expect(isAgentSuggestedModel(provider, createModelItem('Claude Sonnet 4.6'))).toBe(true)
|
||||
|
||||
+78
-3
@@ -1,4 +1,4 @@
|
||||
import type { AddOAuthButtonProps } from '@/app/components/plugins/plugin-auth/types'
|
||||
import type { AddOAuthButtonProps, Credential } from '@/app/components/plugins/plugin-auth/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
@@ -6,6 +6,7 @@ import { act, cleanup, render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
|
||||
@@ -21,6 +22,13 @@ import { AgentTools } from '../index'
|
||||
const toolProviderState = vi.hoisted(() => ({
|
||||
builtInTools: [] as ToolWithProvider[],
|
||||
}))
|
||||
const pluginAuthState = vi.hoisted(() => ({
|
||||
canOAuth: true as boolean | undefined,
|
||||
canApiKey: false as boolean | undefined,
|
||||
credentials: [] as Credential[],
|
||||
notAllowCustomCredential: false,
|
||||
invalidPluginCredentialInfo: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/block-selector/tool-picker', () => ({
|
||||
ToolPickerContent: () => <div>Mock tool picker</div>,
|
||||
@@ -51,6 +59,21 @@ vi.mock('@/app/components/plugins/plugin-auth/authorize/add-oauth-button', () =>
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/plugin-auth/hooks/use-plugin-auth', () => ({
|
||||
usePluginAuth: () => ({
|
||||
...pluginAuthState,
|
||||
isAuthorized: pluginAuthState.credentials.length > 0,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-credential-permissions', () => ({
|
||||
useCredentialPermissions: () => ({
|
||||
canUseCredential: true,
|
||||
canCreateCredential: true,
|
||||
canManageCredential: true,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-modal/Form', () => ({
|
||||
default: ({
|
||||
formSchemas,
|
||||
@@ -333,6 +356,10 @@ describe('AgentTools', () => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
toolProviderState.builtInTools = []
|
||||
pluginAuthState.canOAuth = true
|
||||
pluginAuthState.canApiKey = false
|
||||
pluginAuthState.credentials = []
|
||||
pluginAuthState.notAllowCustomCredential = false
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
@@ -521,7 +548,8 @@ describe('AgentTools', () => {
|
||||
expect(store.get(isAgentComposerDirtyAtom)).toBe(false)
|
||||
})
|
||||
|
||||
it('should show authorization action for reflected OAuth provider tools with unauthorized credential type', () => {
|
||||
it('should open authorization actions for reflected OAuth provider tools', async () => {
|
||||
const user = userEvent.setup()
|
||||
toolProviderState.builtInTools = [
|
||||
{
|
||||
...googleProvider,
|
||||
@@ -537,7 +565,54 @@ describe('AgentTools', () => {
|
||||
name: 'tools.notAuthorized',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.auth.setupOAuth')).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'plugin.auth.useOAuthAuth' }),
|
||||
).not.toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'tools.notAuthorized' }))
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'plugin.auth.useOAuthAuth',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should bind an existing credential selected from the unauthorized status', async () => {
|
||||
const user = userEvent.setup()
|
||||
toolProviderState.builtInTools = [
|
||||
{
|
||||
...googleProvider,
|
||||
allow_delete: true,
|
||||
is_team_authorization: false,
|
||||
team_credentials: {},
|
||||
},
|
||||
]
|
||||
pluginAuthState.credentials = [
|
||||
{
|
||||
id: 'workspace-oauth',
|
||||
name: 'Workspace OAuth',
|
||||
provider: 'google',
|
||||
credential_type: CredentialTypeEnum.OAUTH2,
|
||||
is_default: true,
|
||||
},
|
||||
]
|
||||
const { store } = renderAgentToolsWithStore(reflectedUnauthorizedOAuthCredentialTypeDraft)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'tools.notAuthorized' }))
|
||||
|
||||
expect(store.get(agentComposerDraftAtom).tools[0]).toMatchObject({
|
||||
credentialType: 'unauthorized',
|
||||
credentialVariant: 'none',
|
||||
})
|
||||
|
||||
await user.click(screen.getByText('Workspace OAuth'))
|
||||
|
||||
expect(store.get(agentComposerDraftAtom).tools[0]).toMatchObject({
|
||||
credentialId: 'workspace-oauth',
|
||||
credentialType: 'oauth2',
|
||||
credentialVariant: 'authorized',
|
||||
})
|
||||
})
|
||||
|
||||
it('should open provider tool settings with catalog icon and parameters', async () => {
|
||||
|
||||
+47
-45
@@ -17,11 +17,8 @@ import {
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
||||
import AddOAuthButton from '@/app/components/plugins/plugin-auth/authorize/add-oauth-button'
|
||||
import ApiKeyModal from '@/app/components/plugins/plugin-auth/authorize/api-key-modal'
|
||||
import { AuthCategory, Authorized, usePluginAuth } from '@/app/components/plugins/plugin-auth'
|
||||
import AuthorizedInNode from '@/app/components/plugins/plugin-auth/authorized-in-node'
|
||||
import { useInvalidPluginCredentialInfoHook } from '@/app/components/plugins/plugin-auth/hooks/use-credential'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
@@ -58,7 +55,7 @@ function UnauthorizedCredentialStatus({
|
||||
) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const pluginPayload = useMemo(
|
||||
() => ({
|
||||
provider: tool.id,
|
||||
@@ -67,53 +64,58 @@ function UnauthorizedCredentialStatus({
|
||||
}),
|
||||
[tool.id, tool.providerType],
|
||||
)
|
||||
const invalidPluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
||||
const handleApiKeyModalOpen = useCallback(() => {
|
||||
setIsApiKeyModalOpen(true)
|
||||
}, [])
|
||||
const handleApiKeyModalClose = useCallback(() => {
|
||||
setIsApiKeyModalOpen(false)
|
||||
}, [])
|
||||
const {
|
||||
canApiKey,
|
||||
canOAuth,
|
||||
credentials,
|
||||
invalidPluginCredentialInfo,
|
||||
notAllowCustomCredential,
|
||||
} = usePluginAuth(pluginPayload, true)
|
||||
const handleCredentialUpdate = useCallback(() => {
|
||||
invalidPluginCredentialInfo()
|
||||
onCredentialChange(undefined, tool.credentialType)
|
||||
}, [invalidPluginCredentialInfo, onCredentialChange, tool.credentialType])
|
||||
|
||||
if (tool.credentialType === 'oauth2') {
|
||||
return (
|
||||
<AddOAuthButton
|
||||
pluginPayload={pluginPayload}
|
||||
onUpdate={handleCredentialUpdate}
|
||||
renderTrigger={({ disabled, onClick }) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
className="shrink-0"
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
{t(($) => $.notAuthorized, { ns: 'tools' })}
|
||||
<StatusDot className="ml-2" status="warning" />
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="secondary" size="small" className="shrink-0" onClick={handleApiKeyModalOpen}>
|
||||
const handleAuthorizationItemClick = useCallback(
|
||||
(id: string) => {
|
||||
const credential = credentials.find((item) => item.id === id)
|
||||
onCredentialChange(id, credential?.credential_type ?? tool.credentialType)
|
||||
setIsOpen(false)
|
||||
},
|
||||
[credentials, onCredentialChange, tool.credentialType],
|
||||
)
|
||||
const renderTrigger = useCallback(
|
||||
(open?: boolean) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
className={cn('shrink-0', open && 'bg-components-button-secondary-bg-hover')}
|
||||
>
|
||||
{t(($) => $.notAuthorized, { ns: 'tools' })}
|
||||
<StatusDot className="ml-2" status="warning" />
|
||||
</Button>
|
||||
<ApiKeyModal
|
||||
pluginPayload={pluginPayload}
|
||||
open={isApiKeyModalOpen}
|
||||
onOpenChange={setIsApiKeyModalOpen}
|
||||
onClose={handleApiKeyModalClose}
|
||||
onUpdate={handleCredentialUpdate}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
[t],
|
||||
)
|
||||
|
||||
return (
|
||||
<Authorized
|
||||
pluginPayload={pluginPayload}
|
||||
credentials={credentials}
|
||||
canOAuth={canOAuth}
|
||||
canApiKey={canApiKey}
|
||||
renderTrigger={renderTrigger}
|
||||
isOpen={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
offset={4}
|
||||
placement="bottom-end"
|
||||
triggerPopupSameWidth={false}
|
||||
popupClassName="w-[360px]"
|
||||
disableSetDefault
|
||||
onItemClick={handleAuthorizationItemClick}
|
||||
showItemSelectedIcon
|
||||
onUpdate={handleCredentialUpdate}
|
||||
notAllowCustomCredential={notAllowCustomCredential}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ const agentSuggestedModelPatterns: RegExp[] = [
|
||||
// openai
|
||||
/^gpt[ .-]5\.5$/i,
|
||||
/^gpt[ .-]5\.5[ .-]pro$/i,
|
||||
/^gpt[ .-]5\.6(?:[ .-](?:sol|terra|luna))?$/i,
|
||||
|
||||
// anthropic
|
||||
/^(?:claude[ .-])?opus[ .-]4\.8$/i,
|
||||
|
||||
Reference in New Issue
Block a user