Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions oas_docs/output/kibana.serverless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,13 @@ paths:
description: Configuration settings for the agent.
type: object
properties:
connector_ids:
description: Array of connector IDs to associate with the agent.
items:
description: Connector ID to associate with the agent.
type: string
maxItems: 100
type: array
enable_elastic_capabilities:
description: When true, enables built-in Elastic capabilities for the agent.
type: boolean
Expand Down Expand Up @@ -1906,6 +1913,13 @@ paths:
description: Updated configuration settings for the agent.
type: object
properties:
connector_ids:
description: Array of connector IDs to associate with the agent.
items:
description: Connector ID to associate with the agent.
type: string
maxItems: 100
type: array
enable_elastic_capabilities:
description: When true, enables built-in Elastic capabilities for the agent.
type: boolean
Expand Down
14 changes: 14 additions & 0 deletions oas_docs/output/kibana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,13 @@ paths:
description: Configuration settings for the agent.
type: object
properties:
connector_ids:
description: Array of connector IDs to associate with the agent.
items:
description: Connector ID to associate with the agent.
type: string
maxItems: 100
type: array
enable_elastic_capabilities:
description: When true, enables built-in Elastic capabilities for the agent.
type: boolean
Expand Down Expand Up @@ -1976,6 +1983,13 @@ paths:
description: Updated configuration settings for the agent.
type: object
properties:
connector_ids:
description: Array of connector IDs to associate with the agent.
items:
description: Connector ID to associate with the agent.
type: string
maxItems: 100
type: array
enable_elastic_capabilities:
description: When true, enables built-in Elastic capabilities for the agent.
type: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ export interface AgentConfiguration {
*/
plugin_ids?: string[];

/**
* Optional list of connector IDs associated with this agent.
* When set, SML search filters connector results to only those in this list.
* When undefined, all connectors remain visible (backward compatibility).
*/
connector_ids?: string[];

/**
* Custom configuration for the research step of the agent.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-ser
import type { ToolResult } from '@kbn/agent-builder-common/tools/tool_result';
import type { PromptRequest } from '@kbn/agent-builder-common/agents/prompts';
import type { AgentExecutionMode } from '@kbn/agent-builder-common';
import type { AgentConfiguration } from '@kbn/agent-builder-common';
import type {
ToolEventEmitter,
ModelProvider,
Expand Down Expand Up @@ -167,4 +168,9 @@ export interface ToolHandlerContext {
* When 'standalone', the execution is non-interactive (HITL disabled).
*/
executionMode?: AgentExecutionMode;
/**
* The effective agent configuration for the current run, with any
* runtime configuration overrides already applied.
*/
agentConfiguration?: AgentConfiguration;
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ jest.mock('../../../../../../../hooks/sml/use_sml_search', () => ({
useSmlSearch: () => mockUseSmlSearchReturn,
}));

jest.mock('../../../../../../../hooks/use_conversation', () => ({
useAgentId: () => 'test-agent-id',
}));

jest.mock('../../../../../../../hooks/agents/use_agent_by_id', () => ({
useAgentBuilderAgentById: () => ({ agent: null, isLoading: false, error: null }),
}));

beforeEach(() => {
mockUseSmlSearchReturn = {
results: defaultMockResults,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,22 @@ import React, { forwardRef, useCallback, useMemo } from 'react';
import { css } from '@emotion/react';
import { EuiHighlight, useEuiTheme } from '@elastic/eui';
import { useSmlSearch } from '../../../../../../../hooks/sml/use_sml_search';
import { useAgentId } from '../../../../../../../hooks/use_conversation';
import { useAgentBuilderAgentById } from '../../../../../../../hooks/agents/use_agent_by_id';
import type { CommandMenuComponentProps, CommandMenuHandle } from '../../types';
import { CommandId } from '../../types';
import { getSmlMenuHighlightSearchStrings } from '../../utils/sml_command_menu_highlight';
import { buildSmlFiltersFromAgent } from '../../utils/sml_filters';
import { CommandMenuList } from '../components/command_menu_list';
import type { CommandMenuListOption } from '../components/command_menu_list';

export const Sml = forwardRef<CommandMenuHandle, CommandMenuComponentProps>(
({ query, onSelect }, ref) => {
const agentId = useAgentId();
const { agent } = useAgentBuilderAgentById(agentId);
const filters = useMemo(() => buildSmlFiltersFromAgent(agent), [agent]);
const { euiTheme } = useEuiTheme();
const { results, isLoading } = useSmlSearch(query, { skipContent: true });
const { results, isLoading } = useSmlSearch(query, { skipContent: true, filters });
const { type, title } = useMemo(() => getSmlMenuHighlightSearchStrings(query), [query]);

const smlMenuLabelStyles = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

import { useCallback } from 'react';
import { useQueryClient } from '@kbn/react-query';
import type { SmlSearchFilters } from '@kbn/agent-context-layer-plugin/public';
import { SML_SEARCH_DEFAULT_SIZE } from '../../../../../../../../services/sml/constants';
import { queryKeys } from '../../../../../../../query_keys';
import { useAgentBuilderServices } from '../../../../../../../hooks/use_agent_builder_service';
import { useExperimentalFeatures } from '../../../../../../../hooks/use_experimental_features';

export const usePrefetchSml = () => {
export const usePrefetchSml = (filters?: SmlSearchFilters) => {
const queryClient = useQueryClient();
const { smlService } = useAgentBuilderServices();
const experimentalFeaturesEnabled = useExperimentalFeatures();
Expand All @@ -22,13 +23,14 @@ export const usePrefetchSml = () => {
return;
}
queryClient.prefetchQuery({
queryKey: queryKeys.sml.search('*', true),
queryKey: queryKeys.sml.search('*', true, filters),
queryFn: () =>
smlService.search({
query: '*',
size: SML_SEARCH_DEFAULT_SIZE,
skipContent: true,
filters,
}),
});
}, [experimentalFeaturesEnabled, queryClient, smlService]);
}, [experimentalFeaturesEnabled, queryClient, smlService, filters]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,40 @@
* 2.0.
*/

import { useCallback, useRef } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { usePrefetchSkills } from './menus/skills/use_prefetch_skills';
import { usePrefetchSml } from './menus/sml/use_prefetch_sml';
import { useAgentId } from '../../../../../hooks/use_conversation';
import { useAgentBuilderAgentById } from '../../../../../hooks/agents/use_agent_by_id';
import { buildSmlFiltersFromAgent } from './utils/sml_filters';

/**
* Prefetches data for all command menus on first invocation.
* Re-prefetches SML when the agent's filters change (e.g. after the async
* agent fetch resolves with connector_ids).
* Returns a callback that should be called when the editor receives focus.
*/
const NOT_YET_PREFETCHED = Symbol('not-yet-prefetched');

export const useCommandMenuPrefetch = () => {
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
const hasPrefetched = useRef(false);
const hasPrefetchedSkills = useRef(false);
const lastSmlFiltersJson = useRef<string | symbol>(NOT_YET_PREFETCHED);
const agentId = useAgentId();
const { agent } = useAgentBuilderAgentById(agentId);
const filters = useMemo(() => buildSmlFiltersFromAgent(agent), [agent]);
const prefetchSkills = usePrefetchSkills();
const prefetchSml = usePrefetchSml();
const prefetchSml = usePrefetchSml(filters);

return useCallback(() => {
if (hasPrefetched.current) {
return;
if (!hasPrefetchedSkills.current) {
hasPrefetchedSkills.current = true;
prefetchSkills();
}

const filtersJson = filters ? JSON.stringify(filters) : '';
if (filtersJson !== lastSmlFiltersJson.current) {
lastSmlFiltersJson.current = filtersJson;
prefetchSml();
}
hasPrefetched.current = true;
prefetchSkills();
prefetchSml();
}, [prefetchSkills, prefetchSml]);
}, [prefetchSkills, prefetchSml, filters]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 type { AgentDefinition } from '@kbn/agent-builder-common/agents/definition';
import type { SmlSearchFilters } from '@kbn/agent-context-layer-plugin/public';
import { SmlSearchFilterType } from '@kbn/agent-context-layer-plugin/public';

// Three states: undefined → no filtering (all connectors visible),
// [] → no connectors allowed, ['id1', ...] → only those connectors.
export const buildSmlFiltersFromAgent = (
agent: AgentDefinition | null
): SmlSearchFilters | undefined => {
const connectorIds = agent?.configuration?.connector_ids;
if (connectorIds === undefined) {
return undefined;
}
return { [SmlSearchFilterType.connector]: { ids: connectorIds } };
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useDebouncedValue } from '@kbn/react-hooks';
import { useQuery } from '@kbn/react-query';
import { formatAgentBuilderErrorMessage } from '@kbn/agent-builder-browser';
import { i18n } from '@kbn/i18n';
import type { SmlSearchFilters } from '@kbn/agent-context-layer-plugin/public';
import { SML_SEARCH_DEFAULT_SIZE } from '../../../services/sml/constants';
import { queryKeys } from '../../query_keys';
import { useAgentBuilderServices } from '../use_agent_builder_service';
Expand All @@ -28,23 +29,27 @@ const smlSearchErrorToastTitle = i18n.translate(
export interface UseSmlSearchOptions {
/** When true, the server omits indexed `content` on each hit (smaller payload; e.g. command-menu autocomplete). */
readonly skipContent?: boolean;
/** Per-type filters for SML search. */
readonly filters?: SmlSearchFilters;
}

export const useSmlSearch = (query: string, options?: UseSmlSearchOptions) => {
const { services } = useKibana();
const { smlService } = useAgentBuilderServices();
const debouncedQuery = useDebouncedValue(query, SML_SEARCH_DEBOUNCE_MS);
const skipContent = options?.skipContent ?? false;
const filters = options?.filters;

const searchQuery = useMemo(() => normalizeSmlSearchQuery(debouncedQuery), [debouncedQuery]);

const { isError, isLoading, error, data } = useQuery({
queryKey: queryKeys.sml.search(searchQuery, skipContent),
queryKey: queryKeys.sml.search(searchQuery, skipContent, filters),
queryFn: () =>
smlService.search({
query: searchQuery,
size: SML_SEARCH_DEFAULT_SIZE,
skipContent,
filters,
}),
staleTime: SML_SEARCH_STALE_TIME_MS,
cacheTime: SML_SEARCH_CACHE_TIME_MS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import type { SmlSearchFilters } from '@kbn/agent-context-layer-plugin/public';

/**
* Query keys for react-query
*/
Expand Down Expand Up @@ -51,8 +53,8 @@ export const queryKeys = {
byAgent: (agentId?: string) => ['skills', 'byAgent', agentId],
},
sml: {
search: (query: string, skipContent: boolean) =>
['sml', 'search', { query, skipContent }] as const,
search: (query: string, skipContent: boolean, filters?: SmlSearchFilters) =>
['sml', 'search', { query, skipContent, filters }] as const,
},
plugins: {
all: ['plugins', 'list'] as const,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
*/

import type { HttpSetup } from '@kbn/core-http-browser';
import type { SmlSearchHttpResponse } from '@kbn/agent-context-layer-plugin/public';
import type {
SmlSearchFilters,
SmlSearchHttpResponse,
} from '@kbn/agent-context-layer-plugin/public';
import { smlSearchPath } from '@kbn/agent-context-layer-plugin/public';

/** Browser client for SML search (`/internal/agent_context_layer/sml/_search`). */
Expand All @@ -21,12 +24,14 @@ export class SmlService {
query: string;
size: number;
skipContent?: boolean;
filters?: SmlSearchFilters;
}): Promise<SmlSearchHttpResponse> {
return await this.http.post<SmlSearchHttpResponse>(smlSearchPath, {
body: JSON.stringify({
query: params.query,
size: params.size,
...(params.skipContent === true ? { skip_content: true } : {}),
...(params.filters ? { filters: params.filters } : {}),
}),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ const PLUGINS_SCHEMA = schema.arrayOf(
}
);

const CONNECTORS_SCHEMA = schema.arrayOf(
schema.string({
meta: { description: 'Connector ID to associate with the agent.' },
}),
{
maxSize: 100,
meta: { description: 'Array of connector IDs to associate with the agent.' },
}
);

export function registerAgentRoutes({
router,
getInternalServices,
Expand Down Expand Up @@ -239,6 +249,7 @@ export function registerAgentRoutes({
)
),
plugin_ids: schema.maybe(PLUGINS_SCHEMA),
connector_ids: schema.maybe(CONNECTORS_SCHEMA),
},
{
meta: { description: 'Configuration settings for the agent.' },
Expand Down Expand Up @@ -382,6 +393,7 @@ export function registerAgentRoutes({
)
),
plugin_ids: schema.maybe(PLUGINS_SCHEMA),
connector_ids: schema.maybe(CONNECTORS_SCHEMA),
},
{
meta: { description: 'Updated configuration settings for the agent.' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const fromEs = (document: Document): PersistedAgentDefinition => {
(resolvedId === agentBuilderDefaultAgentId ? true : undefined),
workflow_ids: configuration.workflow_ids,
plugin_ids: configuration.plugin_ids,
connector_ids: configuration.connector_ids,
},
};
};
Expand Down Expand Up @@ -86,6 +87,7 @@ export const createRequestToEs = ({
enable_elastic_capabilities: profile.configuration.enable_elastic_capabilities,
workflow_ids: profile.configuration.workflow_ids,
plugin_ids: profile.configuration.plugin_ids,
connector_ids: profile.configuration.connector_ids,
},
created_at: creationDate.toISOString(),
updated_at: creationDate.toISOString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const storageSettings = {
workflow_ids: types.keyword({}),
plugin_ids: types.keyword({}),
skill_ids: types.keyword({}),
connector_ids: types.keyword({}),
},
dynamic: false,
}),
Expand Down Expand Up @@ -68,6 +69,7 @@ export interface AgentConfigurationProperties {
enable_elastic_capabilities?: boolean;
workflow_ids?: string[];
plugin_ids?: string[];
connector_ids?: string[];
}

export type AgentProfileStorageSettings = typeof storageSettings;
Expand Down
Loading
Loading