chore: new agent enchance (#39040)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Joel
2026-07-16 06:46:41 +00:00
committed by GitHub
co-authored by autofix-ci[bot]
parent 8ff92c9ef8
commit 1a2ba2237d
17 changed files with 541 additions and 73 deletions
@@ -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)
@@ -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 () => {
@@ -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,