diff --git a/x-pack/platform/packages/shared/kbn-inference-connectors/src/use_load_connectors.ts b/x-pack/platform/packages/shared/kbn-inference-connectors/src/use_load_connectors.ts index d05870adcb87d..85191a5540ca4 100644 --- a/x-pack/platform/packages/shared/kbn-inference-connectors/src/use_load_connectors.ts +++ b/x-pack/platform/packages/shared/kbn-inference-connectors/src/use_load_connectors.ts @@ -51,32 +51,30 @@ export const useLoadConnectors = ({ featureId, }: UseLoadConnectorsProps): UseLoadConnectorsResult => { const [soEntryFound, setSoEntryFound] = useState(false); - const query = useQuery( - [...QUERY_KEY, featureId], - async () => { + const query = useQuery({ + queryKey: [...QUERY_KEY, featureId], + queryFn: async () => { const result = await fetchConnectorsForFeature(http, featureId); setSoEntryFound(result.soEntryFound); return result.connectors.map(toAIConnector); }, - { - retry: false, - keepPreviousData: true, - onError: (error: IHttpFetchError) => { - if (error.name !== 'AbortError') { - toasts?.addError( - error.body && (error.body as { message?: string }).message - ? new Error((error.body as { message: string }).message) - : error, - { - title: i18n.translate('inferenceConnectors.useLoadConnectors.errorMessage', { - defaultMessage: 'Error loading models', - }), - } - ); - } - }, - } - ); + retry: false, + keepPreviousData: true, + onError: (error: IHttpFetchError) => { + if (error.name !== 'AbortError') { + toasts?.addError( + error.body && (error.body as { message?: string }).message + ? new Error((error.body as { message: string }).message) + : error, + { + title: i18n.translate('inferenceConnectors.useLoadConnectors.errorMessage', { + defaultMessage: 'Error loading models', + }), + } + ); + } + }, + }); return { ...query, soEntryFound }; }; diff --git a/x-pack/platform/plugins/shared/agent_builder/common/recommended_connectors.test.ts b/x-pack/platform/plugins/shared/agent_builder/common/recommended_connectors.test.ts deleted file mode 100644 index 206f18743c9f8..0000000000000 --- a/x-pack/platform/plugins/shared/agent_builder/common/recommended_connectors.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - RECOMMENDED_CONNECTOR_IDS, - isRecommendedConnector, - getFirstRecommendedConnectorId, -} from './recommended_connectors'; - -describe('recommended_connectors', () => { - describe('RECOMMENDED_CONNECTOR_IDS', () => { - it('includes expected SOTA per provider and open-weight connector IDs', () => { - expect(RECOMMENDED_CONNECTOR_IDS).toContain('Anthropic-Claude-Sonnet-4-5'); - expect(RECOMMENDED_CONNECTOR_IDS).toContain('OpenAI-GPT-5-2'); - expect(RECOMMENDED_CONNECTOR_IDS).toContain('Google-Gemini-2-5-Pro'); - expect(RECOMMENDED_CONNECTOR_IDS).toContain('OpenAI-GPT-OSS-120B'); - }); - }); - - describe('isRecommendedConnector', () => { - it('returns true for IDs in the recommended list', () => { - expect(isRecommendedConnector('Anthropic-Claude-Sonnet-4-5')).toBe(true); - expect(isRecommendedConnector('OpenAI-GPT-OSS-120B')).toBe(true); - }); - - it('returns false for IDs not in the recommended list', () => { - expect(isRecommendedConnector('custom-connector-id')).toBe(false); - expect(isRecommendedConnector('Elastic-Managed-LLM')).toBe(false); - }); - }); - - describe('getFirstRecommendedConnectorId', () => { - it('returns the first recommended ID present in the list (order by RECOMMENDED_CONNECTOR_IDS)', () => { - const connectorIds = ['Google-Gemini-2-5-Pro', 'OpenAI-GPT-5-2', 'custom']; - expect(getFirstRecommendedConnectorId(connectorIds)).toBe('OpenAI-GPT-5-2'); - }); - - it('returns the only recommended ID when one is present', () => { - expect(getFirstRecommendedConnectorId(['other', 'OpenAI-GPT-OSS-120B', 'foo'])).toBe( - 'OpenAI-GPT-OSS-120B' - ); - }); - - it('returns undefined when no recommended connector is in the list', () => { - expect(getFirstRecommendedConnectorId(['custom-1', 'custom-2'])).toBeUndefined(); - }); - - it('returns undefined for empty list', () => { - expect(getFirstRecommendedConnectorId([])).toBeUndefined(); - }); - }); -}); diff --git a/x-pack/platform/plugins/shared/agent_builder/common/recommended_connectors.ts b/x-pack/platform/plugins/shared/agent_builder/common/recommended_connectors.ts deleted file mode 100644 index 679a49e81486d..0000000000000 --- a/x-pack/platform/plugins/shared/agent_builder/common/recommended_connectors.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const RECOMMENDED_CONNECTOR_IDS: readonly string[] = [ - 'Anthropic-Claude-Sonnet-4-5', - 'OpenAI-GPT-5-2', - 'Google-Gemini-2-5-Pro', - 'OpenAI-GPT-OSS-120B', -] as const; - -const RECOMMENDED_SET = new Set(RECOMMENDED_CONNECTOR_IDS); - -export function isRecommendedConnector(connectorId: string): boolean { - return RECOMMENDED_SET.has(connectorId); -} - -export function getFirstRecommendedConnectorId(connectorIds: string[]): string | undefined { - const idSet = new Set(connectorIds); - for (const id of RECOMMENDED_CONNECTOR_IDS) { - if (idSet.has(id)) return id; - } - return undefined; -} diff --git a/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.test.tsx b/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.test.tsx index 73e5cfc195f85..69918dfaceab3 100644 --- a/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.test.tsx +++ b/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.test.tsx @@ -34,10 +34,6 @@ jest.mock('../../../../../hooks/use_ui_privileges', () => ({ useUiPrivileges: () => ({ write: true }), })); -jest.mock('../../../../../../../common/recommended_connectors', () => ({ - isRecommendedConnector: () => false, -})); - jest.mock('../input_actions.styles', () => ({ getMaxListHeight: () => 200, selectorPopoverPanelStyles: undefined, diff --git a/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.tsx b/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.tsx index db18ebdf51b77..93725a1e378b9 100644 --- a/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.tsx +++ b/x-pack/platform/plugins/shared/agent_builder/public/application/components/conversations/conversation_input/input_actions/connector_selector/connector_selector.tsx @@ -24,7 +24,6 @@ import { useNavigation } from '../../../../../hooks/use_navigation'; import { useSendMessage } from '../../../../../context/send_message/send_message_context'; import { useDefaultConnector } from '../../../../../hooks/chat/use_default_connector'; import { useKibana } from '../../../../../hooks/use_kibana'; -import { isRecommendedConnector } from '../../../../../../../common/recommended_connectors'; import { getMaxListHeight, selectorPopoverPanelStyles, @@ -168,13 +167,24 @@ export const ConnectorSelector: React.FC<{}> = () => { const connectors = useMemo(() => aiConnectors ?? [], [aiConnectors]); const { recommendedConnectors, otherConnectors, customConnectors } = useMemo(() => { - const recommended = connectors.filter((c) => isRecommendedConnector(c.id)); - const notRecommended = connectors.filter((c) => !isRecommendedConnector(c.id)); - return { - recommendedConnectors: recommended, - otherConnectors: notRecommended.filter((c) => c.isPreconfigured), - customConnectors: notRecommended.filter((c) => !c.isPreconfigured), - }; + const groupedConnectors = connectors.reduce<{ + recommendedConnectors: typeof connectors; + otherConnectors: typeof connectors; + customConnectors: typeof connectors; + }>( + (acc, c) => { + if (c.isRecommended) { + acc.recommendedConnectors.push(c); + } else if (c.isPreconfigured) { + acc.otherConnectors.push(c); + } else { + acc.customConnectors.push(c); + } + return acc; + }, + { recommendedConnectors: [], otherConnectors: [], customConnectors: [] } + ); + return groupedConnectors; }, [connectors]); const togglePopover = () => setIsPopoverOpen(!isPopoverOpen); diff --git a/x-pack/platform/plugins/shared/agent_builder/public/application/hooks/chat/use_default_connector.ts b/x-pack/platform/plugins/shared/agent_builder/public/application/hooks/chat/use_default_connector.ts index ad90382fe33c7..865512589aa57 100644 --- a/x-pack/platform/plugins/shared/agent_builder/public/application/hooks/chat/use_default_connector.ts +++ b/x-pack/platform/plugins/shared/agent_builder/public/application/hooks/chat/use_default_connector.ts @@ -6,9 +6,8 @@ */ import { useMemo } from 'react'; -import type { AIConnector } from '@kbn/elastic-assistant'; +import type { AIConnector } from '@kbn/inference-connectors'; import { PREFERRED_DEFAULT_CONNECTOR_ID } from '../../../../common/constants'; -import { getFirstRecommendedConnectorId } from '../../../../common/recommended_connectors'; interface UseDefaultConnectorParams { connectors: AIConnector[]; @@ -30,7 +29,8 @@ export function useDefaultConnector({ } // 2. Prefer the first recommended connector when available (SOTA per provider + open-weight) - const recommendedId = getFirstRecommendedConnectorId(connectors.map((c) => c.id)); + const recommendedConnector = connectors.find((c) => c.isRecommended); + const recommendedId = recommendedConnector?.id; if (recommendedId) { return recommendedId; }