From ba7767e8669fd27c234c7ac78e3f81eb20116c85 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 09:15:14 -0600 Subject: [PATCH 01/41] wip --- ...ost_actions_connector_execute_route.gen.ts | 4 +- ...ctions_connector_execute_route.schema.yaml | 2 +- .../impl/schemas/common_attributes.gen.ts | 4 +- .../schemas/common_attributes.schema.yaml | 26 +++++- .../api/chat_complete/post_chat_complete.ts | 4 +- .../api/chat_complete/use_chat_complete.ts | 4 +- .../src/get_prompt.test.ts | 27 ++++++ .../security-ai-prompts/src/get_prompt.ts | 12 +-- .../packages/security-ai-prompts/src/types.ts | 4 +- .../get_prompts_by_prompt_group_id.ts | 89 +++++++++++++++++++ 10 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts index 75b3632d0db04..60b60d880b117 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts @@ -17,7 +17,7 @@ import { z } from '@kbn/zod'; import { BooleanFromString } from '@kbn/zod-helpers'; -import { NonEmptyString, ScreenContext, PromptIds } from '../common_attributes.gen'; +import { NonEmptyString, ScreenContext, PromptIdSet } from '../common_attributes.gen'; import { Replacements } from '../conversations/common_attributes.gen'; export type ExecuteConnectorRequestQuery = z.infer; @@ -56,7 +56,7 @@ export const ExecuteConnectorRequestBody = z.object({ /** * System prompt, will be appended to default system prompt. Different from conversation system prompt, which is retrieved on the server */ - promptIds: PromptIds.optional(), + promptIds: PromptIdSet.optional(), }); export type ExecuteConnectorRequestBodyInput = z.input; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml index 59101648cc431..5b5c8218592e4 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml @@ -72,7 +72,7 @@ paths: screenContext: $ref: '../common_attributes.schema.yaml#/components/schemas/ScreenContext' promptIds: - $ref: '../common_attributes.schema.yaml#/components/schemas/PromptIds' + $ref: '../common_attributes.schema.yaml#/components/schemas/PromptIdSet' description: System prompt, will be appended to default system prompt. Different from conversation system prompt, which is retrieved on the server responses: '200': diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts index fa46c6ff0be38..46a5cf2275902 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts @@ -104,8 +104,8 @@ export const BulkActionBase = z.object({ /** * User screen context. */ -export type PromptIds = z.infer; -export const PromptIds = z.object({ +export type PromptIdSet = z.infer; +export const PromptIdSet = z.object({ /** * The unique identifier for a specific prompt. */ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml index 036584653f770..5f14a313509da 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml @@ -100,8 +100,8 @@ components: - "1234" - "5678" - PromptIds: - description: User screen context. + PromptIdSet: + description: IDs for a specific prompt within a group of prompts. type: object required: - promptId @@ -115,3 +115,25 @@ components: type: string description: The unique identifier for a group of prompts. example: "aiAssistant" + + + PromptIdsSet: + description: Set of prompt IDs for a specific group of prompts. + type: object + required: + - promptIds + - promptGroupId + properties: + promptIds: + type: array + description: Array of IDs for a specific prompts + minItems: 1 + items: + type: string + example: + - "systemPrompt" + - "userPrompt" + promptGroupId: + type: string + description: The unique identifier for a group of prompts. + example: "aiAssistant" diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts index 71c4971f248c6..53c8ec35066be 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts @@ -9,7 +9,7 @@ import { HttpFetchQuery, HttpSetup } from '@kbn/core-http-browser'; import { API_VERSIONS, MessageMetadata, - PromptIds, + PromptIdSet, Replacements, } from '@kbn/elastic-assistant-common'; import { TraceOptions } from '../../types'; @@ -21,7 +21,7 @@ export interface PostChatCompleteParams { connectorId: string; http: HttpSetup; message: string; - promptIds?: PromptIds; + promptIds?: PromptIdSet; replacements: Replacements; query?: HttpFetchQuery; signal?: AbortSignal | undefined; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts index 550ccda6e15aa..85ca803337731 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts @@ -6,14 +6,14 @@ */ import { useCallback, useMemo, useRef, useState } from 'react'; -import { PromptIds, Replacements } from '@kbn/elastic-assistant-common'; +import { PromptIdSet, Replacements } from '@kbn/elastic-assistant-common'; import { HttpFetchQuery } from '@kbn/core-http-browser'; import { ChatCompleteResponse, postChatComplete } from './post_chat_complete'; import { useAssistantContext, useLoadConnectors } from '../../../..'; interface SendMessageProps { message: string; - promptIds?: PromptIds; + promptIds?: PromptIdSet; replacements: Replacements; query?: HttpFetchQuery; } diff --git a/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.test.ts b/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.test.ts index 61ae5aefc6220..1cb6d37619de5 100644 --- a/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.test.ts +++ b/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.test.ts @@ -402,6 +402,17 @@ describe('get_prompt', () => { expect(result).toBe('Hello world this is a system prompt for bedrock claude-3-5-sonnet'); }); + + it('finds the default prompt if no provider/model are indicated and no connector details are provided', async () => { + const result = await getPrompt({ + savedObjectsClient, + localPrompts, + promptId: promptDictionary.systemPrompt, + promptGroupId: promptGroupId.aiAssistant, + }); + + expect(result).toEqual('Hello world this is a system prompt no model, no provider'); + }); }); describe('getPromptsByGroupId', () => { @@ -533,5 +544,21 @@ describe('get_prompt', () => { }) ).rejects.toThrow('Prompt not found for promptId: fake-id and promptGroupId: aiAssistant'); }); + + it('finds the default prompt if no provider/model are indicated and no connector details are provided', async () => { + const result = await getPromptsByGroupId({ + savedObjectsClient, + localPrompts, + promptIds: [promptDictionary.systemPrompt], + promptGroupId: promptGroupId.aiAssistant, + }); + + expect(result).toEqual([ + { + promptId: promptDictionary.systemPrompt, + prompt: 'Hello world this is a system prompt no model, no provider', + }, + ]); + }); }); }); diff --git a/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts b/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts index edf01bb0392ac..d255f6c5aba9d 100644 --- a/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts +++ b/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts @@ -16,7 +16,7 @@ import { promptSavedObjectType } from './saved_object_mappings'; /** * Get prompts by feature (promptGroupId) * provide either model + provider or connector to avoid additional calls to get connector - * @param actionsClient - actions client + * @param actionsClient - actions client (look up connector if connector is not provided) * @param connector - connector, provide if available. No need to provide model and provider in this case * @param connectorId - connector id * @param localPrompts - local prompts object @@ -138,15 +138,17 @@ export const resolveProviderAndModel = async ({ }: { providedProvider?: string; providedModel?: string; - connectorId: string; - actionsClient: PublicMethodsOf; + connectorId?: string; + actionsClient?: PublicMethodsOf; providedConnector?: Connector; }): Promise<{ provider?: string; model?: string }> => { let model = providedModel; let provider = providedProvider; if (!provider || !model || provider === 'inference') { - const connector = providedConnector ?? (await actionsClient.get({ id: connectorId })); - + const connector = providedConnector ?? (await actionsClient?.get({ id: connectorId ?? '' })); + if (!connector) { + return {}; + } if (provider === 'inference' && connector.config) { provider = connector.config.provider || provider; model = connector.config.providerConfig?.model_id || model; diff --git a/x-pack/solutions/security/packages/security-ai-prompts/src/types.ts b/x-pack/solutions/security/packages/security-ai-prompts/src/types.ts index ee2ba36b1d47b..c66b6a4e5674e 100644 --- a/x-pack/solutions/security/packages/security-ai-prompts/src/types.ts +++ b/x-pack/solutions/security/packages/security-ai-prompts/src/types.ts @@ -24,9 +24,9 @@ export interface Prompt { export type PromptArray = Array<{ promptId: string; prompt: string }>; export interface GetPromptArgs { - actionsClient: PublicMethodsOf; + actionsClient?: PublicMethodsOf; connector?: Connector; - connectorId: string; + connectorId?: string; localPrompts: Prompt[]; model?: string; promptId: string; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts new file mode 100644 index 0000000000000..c8ae93e1042d7 --- /dev/null +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts @@ -0,0 +1,89 @@ +/* + * 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 { IKibanaResponse, Logger } from '@kbn/core/server'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +import { API_VERSIONS, ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND } from '@kbn/elastic-assistant-common'; +import { + FindPromptsRequestQuery, + FindPromptsResponse, +} from '@kbn/elastic-assistant-common/impl/schemas'; +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; +import { getPrompt } from '../../lib/prompt'; +import { ElasticAssistantPluginRouter } from '../../types'; +import { buildResponse } from '../utils'; +import { performChecks } from '../helpers'; + +export const getPromptsByPromptGroupId = (router: ElasticAssistantPluginRouter, logger: Logger) => { + router.versioned + .get({ + access: 'public', + path: ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND, + security: { + authz: { + requiredPrivileges: ['elasticAssistant'], + }, + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + query: buildRouteValidationWithZod(FindPromptsRequestQuery), + }, + }, + }, + async (context, request, response): Promise> => { + const assistantResponse = buildResponse(response); + + try { + const { query } = request; + const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); + // Perform license and authenticated user checks + const checkResponse = await performChecks({ + context: ctx, + request, + response, + }); + if (!checkResponse.isSuccess) { + return checkResponse.response; + } + const actions = ctx.elasticAssistant.actions; + const actionsClient = await actions.getActionsClientWithRequest(request); + const savedObjectsClient = ctx.elasticAssistant.savedObjectsClient; + + const prompts = await getPrompt({ + actionsClient, + connectorId: request.body.connectorId, + // promptIds is promptId and promptGroupId + ...promptIds, + savedObjectsClient, + }); + + if (result) { + return response.ok({ + body: { + total: prompts.length, + data: prompts, + }, + }); + } + return response.ok({ + body: { perPage: query.per_page, page: query.page, data: [], total: 0 }, + }); + } catch (err) { + const error = transformError(err); + return assistantResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; From c80fb1e0e73c4ab04f2dc4db153183b8a8b8ce1c Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 10:59:11 -0600 Subject: [PATCH 02/41] why --- .../kbn-elastic-assistant-common/constants.ts | 6 + .../impl/schemas/common_attributes.gen.ts | 2 +- .../schemas/common_attributes.schema.yaml | 22 ---- .../impl/schemas/index.ts | 2 + .../common_attributes.gen.ts | 44 ++++++++ .../common_attributes.schema.yaml | 46 ++++++++ .../find_prompts_route.gen.ts | 44 ++++++++ .../find_prompts_route.schema.yaml | 63 +++++++++++ .../security_ai_prompts/use_find_prompts.ts | 106 ++++++++++++++++++ .../server/lib/prompt/local_prompt_object.ts | 25 +++++ .../server/routes/register_routes.ts | 4 + ..._by_prompt_group_id.ts => find_prompts.ts} | 55 +++++---- .../content/prompt_contexts/index.tsx | 18 ++- .../public/assistant/provider.tsx | 27 ++++- 14 files changed, 406 insertions(+), 58 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts rename x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/{get_prompts_by_prompt_group_id.ts => find_prompts.ts} (63%) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts index 2dc7799e7bc6f..ab1cd0204a8bc 100755 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts @@ -67,6 +67,12 @@ export const ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL_BULK_ACTION = export const ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL_FIND = `${ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL}/_find` as const; +// Security AI Prompts (prompt integraiton +export const ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL = + `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/security_ai_prompts` as const; +export const ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND = + `${ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL}/_find` as const; + // Defend insights export const DEFEND_INSIGHTS_ID = 'defend-insights'; export const DEFEND_INSIGHTS = `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/defend_insights`; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts index 46a5cf2275902..7a881aafee5d4 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts @@ -102,7 +102,7 @@ export const BulkActionBase = z.object({ }); /** - * User screen context. + * IDs for a specific prompt within a group of prompts. */ export type PromptIdSet = z.infer; export const PromptIdSet = z.object({ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml index 5f14a313509da..e63907b6b1c51 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml @@ -115,25 +115,3 @@ components: type: string description: The unique identifier for a group of prompts. example: "aiAssistant" - - - PromptIdsSet: - description: Set of prompt IDs for a specific group of prompts. - type: object - required: - - promptIds - - promptGroupId - properties: - promptIds: - type: array - description: Array of IDs for a specific prompts - minItems: 1 - items: - type: string - example: - - "systemPrompt" - - "userPrompt" - promptGroupId: - type: string - description: The unique identifier for a group of prompts. - example: "aiAssistant" diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/index.ts index 242b6c8e70749..704821b9e1dc6 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/index.ts @@ -75,4 +75,6 @@ export * from './prompts/find_prompts_route.gen'; export * from './alert_summary/bulk_crud_alert_summary_route.gen'; export * from './alert_summary/find_alert_summary_route.gen'; +export * from './security_ai_prompts/find_prompts_route.gen'; + export { PromptResponse, PromptTypeEnum } from './prompts/bulk_crud_prompts_route.gen'; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts new file mode 100644 index 0000000000000..50e9ab61b4299 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Common Security AI Prompts Attributes + * version: not applicable + */ + +import { z } from '@kbn/zod'; + +/** + * Set of prompt IDs for a specific group of prompts. + */ +export type PromptIdsSet = z.infer; +export const PromptIdsSet = z.object({ + /** + * Array of IDs for a specific prompts + */ + promptIds: z.array(z.string()).min(1), + /** + * The unique identifier for a group of prompts. + */ + promptGroupId: z.string(), +}); + +export type PromptItem = z.infer; +export const PromptItem = z.object({ + promptId: z.string(), + prompt: z.string(), +}); + +/** + * Prompt array by prompt group id and prompt id. + */ +export type PromptItemArray = z.infer; +export const PromptItemArray = z.array(PromptItem); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml new file mode 100644 index 0000000000000..3835f0279ca62 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml @@ -0,0 +1,46 @@ +openapi: 3.0.0 +info: + title: Common Security AI Prompts Attributes + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + PromptIdsSet: + description: Set of prompt IDs for a specific group of prompts. + type: object + required: + - promptIds + - promptGroupId + properties: + promptIds: + type: array + description: Array of IDs for a specific prompts + minItems: 1 + items: + type: string + example: + - "systemPrompt" + - "userPrompt" + promptGroupId: + type: string + description: The unique identifier for a group of prompts. + example: "aiAssistant" + PromptItem: + type: object + properties: + promptId: + type: string + example: systemPrompt + prompt: + type: string + example: This is the prompt + required: + - promptId + - prompt + + PromptItemArray: + type: array + description: Prompt array by prompt group id and prompt id. + items: + $ref: '#/components/schemas/PromptItem' diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts new file mode 100644 index 0000000000000..55ebadf0e6704 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Security AI Prompt endpoint + * version: 1 + */ + +import { z } from '@kbn/zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; + +import { PromptItemArray } from './common_attributes.gen'; + +export type FindSecurityAIPromptsRequestQuery = z.infer; +export const FindSecurityAIPromptsRequestQuery = z.object({ + /** + * Connector id used for prompt lookup + */ + connector_id: z.string().optional(), + /** + * The unique identifier for the prompt group + */ + prompt_group_id: z.string(), + /** + * Comma-separated list of prompt IDs to retrieve + */ + prompt_ids: ArrayFromString(z.string()), +}); +export type FindSecurityAIPromptsRequestQueryInput = z.input< + typeof FindSecurityAIPromptsRequestQuery +>; + +export type FindSecurityAIPromptsResponse = z.infer; +export const FindSecurityAIPromptsResponse = z.object({ + prompts: PromptItemArray, +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml new file mode 100644 index 0000000000000..837cfd6f249a8 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml @@ -0,0 +1,63 @@ +openapi: 3.0.0 +info: + title: Get Security AI Prompt endpoint + version: '1' +paths: + /internal/elastic_assistant/security_ai_prompts/_find: + get: + x-codegen-enabled: true + x-internal: true + x-labels: [ess, serverless] + operationId: FindSecurityAIPrompts + description: Gets Security AI prompts + summary: Gets Security AI prompts + tags: + - Security AI Prompts API + parameters: + - name: connector_id + in: query + description: Connector id used for prompt lookup + required: false + schema: + type: string + - name: prompt_group_id + in: query + description: The unique identifier for the prompt group + required: true + schema: + type: string + - name: prompt_ids + in: query + description: Comma-separated list of prompt IDs to retrieve + required: true + style: form + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + prompts: + $ref: './common_attributes.schema.yaml#/components/schemas/PromptItemArray' + required: + - prompts + '400': + description: Generic Error + content: + application/json: + schema: + type: object + properties: + status_code: + type: number + error: + type: string + message: + type: string diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts new file mode 100644 index 0000000000000..8d49a0d2e823e --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts @@ -0,0 +1,106 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { HttpSetup, IToasts } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + FindSecurityAIPromptsRequestQuery, + FindSecurityAIPromptsResponse, +} from '@kbn/elastic-assistant-common'; + +export interface UseFindPromptsParams { + context: { + isAssistantEnabled: boolean; + http: HttpSetup; + toasts: IToasts; + }; + signal?: AbortSignal | undefined; + params: FindSecurityAIPromptsRequestQuery; +} + +/** + * API call for fetching prompts for current spaceId + * + * @param {Object} options - The options object. + * @param {string} options.consumer - prompt consumer + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {useQuery} hook for getting the status of the prompts + */ + +export const useFindPrompts = (payload: UseFindPromptsParams) => { + const { isAssistantEnabled, http, toasts } = payload.context; + + const QUERY = { + connector_id: payload.params.connector_id, + prompt_ids: payload.params.prompt_ids, + prompt_group_id: payload.params.prompt_group_id, + }; + + const CACHING_KEYS = [ + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + QUERY.connector_id, + QUERY.prompt_ids, + QUERY.prompt_group_id, + API_VERSIONS.internal.v1, + ]; + + return useQuery( + CACHING_KEYS, + async () => + getPrompts({ + http, + signal: payload.signal, + toasts, + query: QUERY, + }), + { + initialData: { + prompts: [], + }, + placeholderData: { + prompts: [], + }, + keepPreviousData: true, + enabled: isAssistantEnabled, + } + ); +}; + +const getPrompts = async ({ + http, + signal, + toasts, + query, +}: { + http: HttpSetup; + toasts: IToasts; + signal?: AbortSignal | undefined; + query: FindSecurityAIPromptsRequestQuery; +}) => { + try { + return await http.fetch( + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + { + method: 'GET', + version: API_VERSIONS.internal.v1, + signal, + query, + } + ); + } catch (error) { + toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, { + title: i18n.translate('xpack.elasticAssistant.securityAiPrompts.getPromptsError', { + defaultMessage: 'Error fetching Security AI Prompts', + }), + }); + throw error; + } +}; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts index 331c36212bde1..25d60a65bdba8 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts @@ -38,6 +38,9 @@ export const promptGroupId = { }; export const promptDictionary = { + alertEvaluation: `alertEvaluation`, + dataQualityAnalysis: 'dataQualityAnalysis', + ruleAnalysis: 'ruleAnalysis', alertSummary: `alertSummary`, alertSummarySystemPrompt: `alertSummarySystemPrompt`, systemPrompt: `systemPrompt`, @@ -61,6 +64,7 @@ export const promptDictionary = { defendInsightsIncompatibleAntivirusEventsEndpointId: 'defendInsights-incompatibleAntivirusEventsEndpointId', defendInsightsIncompatibleAntivirusEventsValue: 'defendInsights-incompatibleAntivirusEventsValue', + // context prompts }; export const localPrompts: Prompt[] = [ @@ -259,4 +263,25 @@ export const localPrompts: Prompt[] = [ default: ALERT_SUMMARY_SYSTEM_PROMPT, }, }, + { + promptId: promptDictionary.alertEvaluation, + promptGroupId: promptGroupId.aiAssistant, + prompt: { + default: 'ALERT EVALUATION', + }, + }, + { + promptId: promptDictionary.dataQualityAnalysis, + promptGroupId: promptGroupId.aiAssistant, + prompt: { + default: 'DATA QUALITY ANALYSIS', + }, + }, + { + promptId: promptDictionary.ruleAnalysis, + promptGroupId: promptGroupId.aiAssistant, + prompt: { + default: 'RULE ANALYSIS', + }, + }, ]; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/register_routes.ts index f856491958537..e41644d550abc 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/register_routes.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/register_routes.ts @@ -7,6 +7,7 @@ import type { Logger } from '@kbn/core/server'; +import { findSecurityAIPromptsRoute } from './security_ai_prompts/find_prompts'; import { findAlertSummaryRoute } from './alert_summary/find_route'; import { cancelAttackDiscoveryRoute } from './attack_discovery/post/cancel/cancel_attack_discovery'; import { findAttackDiscoveriesRoute } from './attack_discovery/get/find_attack_discoveries'; @@ -108,6 +109,9 @@ export const registerRoutes = ( bulkPromptsRoute(router, logger); findPromptsRoute(router, logger); + // Security AI Prompts + findSecurityAIPromptsRoute(router, logger); + // Anonymization Fields bulkActionAnonymizationFieldsRoute(router, logger); findAnonymizationFieldsRoute(router, logger); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts similarity index 63% rename from x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts rename to x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts index c8ae93e1042d7..9813f10860b32 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/get_prompts_by_prompt_group_id.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts @@ -8,22 +8,26 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { API_VERSIONS, ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND } from '@kbn/elastic-assistant-common'; import { - FindPromptsRequestQuery, - FindPromptsResponse, -} from '@kbn/elastic-assistant-common/impl/schemas'; + API_VERSIONS, + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + FindSecurityAIPromptsRequestQuery, + FindSecurityAIPromptsResponse, +} from '@kbn/elastic-assistant-common'; import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; -import { getPrompt } from '../../lib/prompt'; +import { getPromptsByGroupId } from '../../lib/prompt'; import { ElasticAssistantPluginRouter } from '../../types'; import { buildResponse } from '../utils'; import { performChecks } from '../helpers'; -export const getPromptsByPromptGroupId = (router: ElasticAssistantPluginRouter, logger: Logger) => { +export const findSecurityAIPromptsRoute = ( + router: ElasticAssistantPluginRouter, + logger: Logger +) => { router.versioned .get({ - access: 'public', - path: ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND, + access: 'internal', + path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, security: { authz: { requiredPrivileges: ['elasticAssistant'], @@ -32,14 +36,23 @@ export const getPromptsByPromptGroupId = (router: ElasticAssistantPluginRouter, }) .addVersion( { - version: API_VERSIONS.public.v1, + version: API_VERSIONS.internal.v1, validate: { request: { - query: buildRouteValidationWithZod(FindPromptsRequestQuery), + query: buildRouteValidationWithZod(FindSecurityAIPromptsRequestQuery), + }, + response: { + 200: { + body: { custom: buildRouteValidationWithZod(FindSecurityAIPromptsResponse) }, + }, }, }, }, - async (context, request, response): Promise> => { + async ( + context, + request, + response + ): Promise> => { const assistantResponse = buildResponse(response); try { @@ -58,24 +71,18 @@ export const getPromptsByPromptGroupId = (router: ElasticAssistantPluginRouter, const actionsClient = await actions.getActionsClientWithRequest(request); const savedObjectsClient = ctx.elasticAssistant.savedObjectsClient; - const prompts = await getPrompt({ + const prompts = await getPromptsByGroupId({ actionsClient, - connectorId: request.body.connectorId, - // promptIds is promptId and promptGroupId - ...promptIds, + connectorId: query.connector_id, + promptGroupId: query.prompt_group_id, + promptIds: query.prompt_ids, savedObjectsClient, }); - if (result) { - return response.ok({ - body: { - total: prompts.length, - data: prompts, - }, - }); - } return response.ok({ - body: { perPage: query.per_page, page: query.page, data: [], total: 0 }, + body: { + prompts, + }, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/index.tsx index 8c05cd0c9f68e..eea83373cac49 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/index.tsx @@ -10,13 +10,11 @@ import * as i18nDataQuality from '@kbn/ecs-data-quality-dashboard'; import * as i18n from './translations'; import * as i18nDetections from '../../../detection_engine/common/translations'; import * as i18nEventDetails from '../../../common/components/event_details/translations'; -import * as i18nUserPrompts from '../prompts/user/translations'; export const PROMPT_CONTEXT_ALERT_CATEGORY = 'alert'; export const PROMPT_CONTEXT_EVENT_CATEGORY = 'event'; export const PROMPT_CONTEXT_DETECTION_RULES_CATEGORY = 'detection-rules'; export const DATA_QUALITY_DASHBOARD_CATEGORY = 'data-quality-dashboard'; -export const KNOWLEDGE_BASE_CATEGORY = 'knowledge-base'; /** * Global list of PromptContexts intended to be used throughout Security Solution. @@ -24,14 +22,15 @@ export const KNOWLEDGE_BASE_CATEGORY = 'knowledge-base'; * a unique set of categories to reference since the PromptContexts available on * useAssistantContext are dynamic (not globally registered). */ -export const PROMPT_CONTEXTS: Record = { +export const getPromptContexts = ( + prompts: Record +): Record => ({ /** * Alert summary view context, made available on the alert details flyout */ [PROMPT_CONTEXT_ALERT_CATEGORY]: { category: PROMPT_CONTEXT_ALERT_CATEGORY, - suggestedUserPrompt: - i18nUserPrompts.EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N, + suggestedUserPrompt: prompts[PROMPT_CONTEXT_ALERT_CATEGORY], description: i18nEventDetails.ALERT_SUMMARY_CONTEXT_DESCRIPTION(i18n.VIEW), tooltip: i18nEventDetails.ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP, }, @@ -40,8 +39,7 @@ export const PROMPT_CONTEXTS: Record> = ({ children }) const alertsIndexPattern = signalIndexName ?? undefined; const toasts = useAppToasts() as unknown as IToasts; // useAppToasts is the current, non-deprecated method of getting the toasts service in the Security Solution, but it doesn't return the IToasts interface (defined by core) + const fetchedPrompts = useFindPrompts({ + context: { + isAssistantEnabled: assistantAvailability.isAssistantEnabled, + http, + toasts, + }, + params: { + prompt_group_id: 'aiAssistant', + prompt_ids: ['alertEvaluation', 'dataQualityAnalysis', 'ruleAnalysis'], + }, + }); + console.log('fetchedPrompts', fetchedPrompts); + const PROMPT_CONTEXTS = getPromptContexts({ + [PROMPT_CONTEXT_ALERT_CATEGORY]: 'string', + [PROMPT_CONTEXT_EVENT_CATEGORY]: 'string', + [DATA_QUALITY_DASHBOARD_CATEGORY]: 'string', + [PROMPT_CONTEXT_DETECTION_RULES_CATEGORY]: 'string', + }); return ( Date: Mon, 23 Jun 2025 11:00:16 -0600 Subject: [PATCH 03/41] undo rename --- .../post_actions_connector_execute_route.gen.ts | 4 ++-- .../post_actions_connector_execute_route.schema.yaml | 2 +- .../impl/schemas/common_attributes.gen.ts | 4 ++-- .../impl/schemas/common_attributes.schema.yaml | 2 +- .../impl/assistant/api/chat_complete/post_chat_complete.ts | 4 ++-- .../impl/assistant/api/chat_complete/use_chat_complete.ts | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts index 60b60d880b117..75b3632d0db04 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen.ts @@ -17,7 +17,7 @@ import { z } from '@kbn/zod'; import { BooleanFromString } from '@kbn/zod-helpers'; -import { NonEmptyString, ScreenContext, PromptIdSet } from '../common_attributes.gen'; +import { NonEmptyString, ScreenContext, PromptIds } from '../common_attributes.gen'; import { Replacements } from '../conversations/common_attributes.gen'; export type ExecuteConnectorRequestQuery = z.infer; @@ -56,7 +56,7 @@ export const ExecuteConnectorRequestBody = z.object({ /** * System prompt, will be appended to default system prompt. Different from conversation system prompt, which is retrieved on the server */ - promptIds: PromptIdSet.optional(), + promptIds: PromptIds.optional(), }); export type ExecuteConnectorRequestBodyInput = z.input; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml index 5b5c8218592e4..59101648cc431 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.schema.yaml @@ -72,7 +72,7 @@ paths: screenContext: $ref: '../common_attributes.schema.yaml#/components/schemas/ScreenContext' promptIds: - $ref: '../common_attributes.schema.yaml#/components/schemas/PromptIdSet' + $ref: '../common_attributes.schema.yaml#/components/schemas/PromptIds' description: System prompt, will be appended to default system prompt. Different from conversation system prompt, which is retrieved on the server responses: '200': diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts index 7a881aafee5d4..d62e93e6a59b1 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts @@ -104,8 +104,8 @@ export const BulkActionBase = z.object({ /** * IDs for a specific prompt within a group of prompts. */ -export type PromptIdSet = z.infer; -export const PromptIdSet = z.object({ +export type PromptIds = z.infer; +export const PromptIds = z.object({ /** * The unique identifier for a specific prompt. */ diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml index e63907b6b1c51..b98ef548c709f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml @@ -100,7 +100,7 @@ components: - "1234" - "5678" - PromptIdSet: + PromptIds: description: IDs for a specific prompt within a group of prompts. type: object required: diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts index 53c8ec35066be..71c4971f248c6 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/post_chat_complete.ts @@ -9,7 +9,7 @@ import { HttpFetchQuery, HttpSetup } from '@kbn/core-http-browser'; import { API_VERSIONS, MessageMetadata, - PromptIdSet, + PromptIds, Replacements, } from '@kbn/elastic-assistant-common'; import { TraceOptions } from '../../types'; @@ -21,7 +21,7 @@ export interface PostChatCompleteParams { connectorId: string; http: HttpSetup; message: string; - promptIds?: PromptIdSet; + promptIds?: PromptIds; replacements: Replacements; query?: HttpFetchQuery; signal?: AbortSignal | undefined; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts index 85ca803337731..550ccda6e15aa 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/chat_complete/use_chat_complete.ts @@ -6,14 +6,14 @@ */ import { useCallback, useMemo, useRef, useState } from 'react'; -import { PromptIdSet, Replacements } from '@kbn/elastic-assistant-common'; +import { PromptIds, Replacements } from '@kbn/elastic-assistant-common'; import { HttpFetchQuery } from '@kbn/core-http-browser'; import { ChatCompleteResponse, postChatComplete } from './post_chat_complete'; import { useAssistantContext, useLoadConnectors } from '../../../..'; interface SendMessageProps { message: string; - promptIds?: PromptIdSet; + promptIds?: PromptIds; replacements: Replacements; query?: HttpFetchQuery; } From 4c64b5fbd7e1070564f32e4585dd87a7d6b1a656 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 11:48:46 -0600 Subject: [PATCH 04/41] public api --- .../shared/kbn-elastic-assistant-common/constants.ts | 4 ++-- .../security_ai_prompts/find_prompts_route.schema.yaml | 4 ++-- .../api/security_ai_prompts/use_find_prompts.ts | 4 ++-- .../packages/security-ai-prompts/src/get_prompt.ts | 5 ++++- .../server/routes/security_ai_prompts/find_prompts.ts | 10 +++------- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts index ab1cd0204a8bc..b87396a44f361 100755 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/constants.ts @@ -67,9 +67,9 @@ export const ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL_BULK_ACTION = export const ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL_FIND = `${ELASTIC_AI_ASSISTANT_ALERT_SUMMARY_URL}/_find` as const; -// Security AI Prompts (prompt integraiton +// Security AI Prompts (prompt integration) export const ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL = - `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/security_ai_prompts` as const; + `${ELASTIC_AI_ASSISTANT_URL}/security_ai_prompts` as const; export const ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND = `${ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL}/_find` as const; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml index 837cfd6f249a8..623ec6da82a72 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.schema.yaml @@ -1,9 +1,9 @@ openapi: 3.0.0 info: title: Get Security AI Prompt endpoint - version: '1' + version: '2023-10-31' paths: - /internal/elastic_assistant/security_ai_prompts/_find: + /api/security_ai_assistant/security_ai_prompts/_find: get: x-codegen-enabled: true x-internal: true diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts index 8d49a0d2e823e..c412e5bef45ce 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts @@ -49,7 +49,7 @@ export const useFindPrompts = (payload: UseFindPromptsParams) => { QUERY.connector_id, QUERY.prompt_ids, QUERY.prompt_group_id, - API_VERSIONS.internal.v1, + API_VERSIONS.public.v1, ]; return useQuery( @@ -90,7 +90,7 @@ const getPrompts = async ({ ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, { method: 'GET', - version: API_VERSIONS.internal.v1, + version: API_VERSIONS.public.v1, signal, query, } diff --git a/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts b/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts index d255f6c5aba9d..9e122dfec276c 100644 --- a/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts +++ b/x-pack/solutions/security/packages/security-ai-prompts/src/get_prompt.ts @@ -145,7 +145,10 @@ export const resolveProviderAndModel = async ({ let model = providedModel; let provider = providedProvider; if (!provider || !model || provider === 'inference') { - const connector = providedConnector ?? (await actionsClient?.get({ id: connectorId ?? '' })); + let connector = providedConnector; + if (!connector && connectorId != null && actionsClient) { + connector = await actionsClient.get({ id: connectorId }); + } if (!connector) { return {}; } diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts index 9813f10860b32..596b747d2d582 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.ts @@ -20,13 +20,10 @@ import { ElasticAssistantPluginRouter } from '../../types'; import { buildResponse } from '../utils'; import { performChecks } from '../helpers'; -export const findSecurityAIPromptsRoute = ( - router: ElasticAssistantPluginRouter, - logger: Logger -) => { +export const findSecurityAIPromptsRoute = (router: ElasticAssistantPluginRouter, logger: Logger) => router.versioned .get({ - access: 'internal', + access: 'public', path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, security: { authz: { @@ -36,7 +33,7 @@ export const findSecurityAIPromptsRoute = ( }) .addVersion( { - version: API_VERSIONS.internal.v1, + version: API_VERSIONS.public.v1, validate: { request: { query: buildRouteValidationWithZod(FindSecurityAIPromptsRequestQuery), @@ -93,4 +90,3 @@ export const findSecurityAIPromptsRoute = ( } } ); -}; From 439f26570c40ea02e8dd96da7fa4b617f5f0436b Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 12:34:24 -0600 Subject: [PATCH 05/41] finished code, needs tests --- .../security_ai_prompts/use_find_prompts.ts | 14 +-- .../shared/kbn-elastic-assistant/index.ts | 2 + .../data_quality_panel/actions/chat/index.tsx | 32 +++++- .../impl/data_quality_panel/translations.ts | 4 +- .../server/lib/prompt/local_prompt_object.ts | 9 +- .../server/lib/prompt/prompts.ts | 37 ++++++ .../use_find_prompt_contexts.test.tsx | 108 ++++++++++++++++++ .../use_find_prompt_contexts.tsx | 52 +++++++++ .../content/prompts/user/translations.ts | 39 ++++--- .../public/assistant/provider.tsx | 20 +--- .../detection_engine/common/translations.ts | 11 +- .../e2e/ai_assistant/conversations.cy.ts | 12 +- .../cypress/e2e/ai_assistant/prompts.cy.ts | 5 - 13 files changed, 279 insertions(+), 66 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.tsx diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts index c412e5bef45ce..00a93c99a2511 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts @@ -6,7 +6,7 @@ */ import { useQuery } from '@tanstack/react-query'; -import { HttpSetup, IToasts } from '@kbn/core/public'; +import { HttpHandler, IToasts } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { API_VERSIONS, @@ -18,7 +18,7 @@ import { export interface UseFindPromptsParams { context: { isAssistantEnabled: boolean; - http: HttpSetup; + httpFetch: HttpHandler; toasts: IToasts; }; signal?: AbortSignal | undefined; @@ -36,7 +36,7 @@ export interface UseFindPromptsParams { */ export const useFindPrompts = (payload: UseFindPromptsParams) => { - const { isAssistantEnabled, http, toasts } = payload.context; + const { isAssistantEnabled, httpFetch, toasts } = payload.context; const QUERY = { connector_id: payload.params.connector_id, @@ -56,7 +56,7 @@ export const useFindPrompts = (payload: UseFindPromptsParams) => { CACHING_KEYS, async () => getPrompts({ - http, + httpFetch, signal: payload.signal, toasts, query: QUERY, @@ -75,18 +75,18 @@ export const useFindPrompts = (payload: UseFindPromptsParams) => { }; const getPrompts = async ({ - http, + httpFetch, signal, toasts, query, }: { - http: HttpSetup; + httpFetch: HttpHandler; toasts: IToasts; signal?: AbortSignal | undefined; query: FindSecurityAIPromptsRequestQuery; }) => { try { - return await http.fetch( + return await httpFetch( ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, { method: 'GET', diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index e4ac385e07846..913ad294adf1c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -183,3 +183,5 @@ export { getNewSelectedPromptContext } from './impl/data_anonymization/get_new_s export { getCombinedMessage } from './impl/assistant/prompt/helpers'; export { useChatComplete } from './impl/assistant/api/chat_complete/use_chat_complete'; export { useFetchAnonymizationFields } from './impl/assistant/api/anonymization_fields/use_fetch_anonymization_fields'; + +export { useFindPrompts } from './impl/assistant/api/security_ai_prompts/use_find_prompts'; diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx index 3ecbbee9a2433..fad7e80064bbb 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import React, { FC, useCallback, useMemo } from 'react'; -import { NewChat } from '@kbn/elastic-assistant'; +import type { FC } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { NewChat, useFindPrompts } from '@kbn/elastic-assistant'; import { css } from '@emotion/react'; import { useEuiTheme } from '@elastic/eui'; @@ -44,8 +45,29 @@ const ChatActionComponent: FC = ({ indexName, markdownComment, checkedAt }, [checkedAt, indexName]); const styles = useStyles(); - const { isAssistantEnabled } = useDataQualityContext(); + const { isAssistantEnabled, httpFetch, toasts } = useDataQualityContext(); + const { + data: { prompts }, + } = useFindPrompts({ + context: { + isAssistantEnabled, + httpFetch, + toasts, + }, + params: { + prompt_group_id: 'aiAssistant', + prompt_ids: ['dataQualityAnalysis'], + }, + }); const getPromptContext = useCallback(async () => markdownComment, [markdownComment]); + + const suggestedUserPrompt = useMemo( + () => + prompts.find(({ promptId }) => promptId === 'dataQualityAnalysis')?.prompt ?? + DATA_QUALITY_SUGGESTED_USER_PROMPT, + [prompts] + ); + return ( = ({ indexName, markdownComment, checkedAt conversationTitle={chatTitle ?? DATA_QUALITY_DASHBOARD_CONVERSATION_ID} description={DATA_QUALITY_PROMPT_CONTEXT_PILL(indexName)} getPromptContext={getPromptContext} - suggestedUserPrompt={DATA_QUALITY_SUGGESTED_USER_PROMPT} + suggestedUserPrompt={suggestedUserPrompt} tooltip={DATA_QUALITY_PROMPT_CONTEXT_PILL_TOOLTIP} isAssistantEnabled={isAssistantEnabled} iconType={null} > - + {ASK_ASSISTANT} diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts index 93150b79138dc..e249fe3fb567f 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts @@ -78,10 +78,10 @@ export const DATA_QUALITY_SUBTITLE: string = i18n.translate( ); export const DATA_QUALITY_SUGGESTED_USER_PROMPT = i18n.translate( - 'securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt', + 'securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPromptV2', { defaultMessage: - 'Explain the results above, and describe some options to fix incompatibilities.', + 'Explain the ECS incompatibility results above, and describe some options to fix incompatibilities. In your explanation, include information about remapping fields, reindexing data, and modifying data ingestion pipelines. Also, describe how ES|QL can be used to identify and correct incompatible data, including examples of using RENAME, EVAL, DISSECT, GROK, and CASE functions.' } ); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts index 25d60a65bdba8..703e29c8f30a4 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts @@ -26,6 +26,9 @@ import { DEFEND_INSIGHTS, ALERT_SUMMARY_500, ALERT_SUMMARY_SYSTEM_PROMPT, + RULE_ANALYSIS, + DATA_QUALITY_ANALYSIS, + ALERT_EVALUATION, } from './prompts'; export const promptGroupId = { @@ -267,21 +270,21 @@ export const localPrompts: Prompt[] = [ promptId: promptDictionary.alertEvaluation, promptGroupId: promptGroupId.aiAssistant, prompt: { - default: 'ALERT EVALUATION', + default: ALERT_EVALUATION, }, }, { promptId: promptDictionary.dataQualityAnalysis, promptGroupId: promptGroupId.aiAssistant, prompt: { - default: 'DATA QUALITY ANALYSIS', + default: DATA_QUALITY_ANALYSIS, }, }, { promptId: promptDictionary.ruleAnalysis, promptGroupId: promptGroupId.aiAssistant, prompt: { - default: 'RULE ANALYSIS', + default: RULE_ANALYSIS, }, }, ]; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts index 2a1682ea19272..94ac4c2af1579 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts @@ -218,3 +218,40 @@ export const ALERT_SUMMARY_SYSTEM_PROMPT = '\n' + 'The response should look like this:\n' + '{{"summary":"Markdown-formatted summary text.","recommendedActions":"Markdown-formatted action list starting with a ### header."}}'; + +export const RULE_ANALYSIS = + 'Please provide a comprehensive analysis of each selected Elastic Security detection rule. For each rule, include:\n' + + '- The rule name and a brief summary of its purpose.\n' + + '- The full detection query as published in Elastic’s official detection rules repository.\n' + + '- An in-depth explanation of how the query works, including key fields, logic, and detection techniques.\n' + + '- The relevance of the rule to modern threats or attack techniques (e.g., MITRE ATT&CK mapping).\n' + + '- Typical implications and recommended response actions for an organization if this rule triggers.\n' + + '- Any notable false positive considerations or tuning recommendations.\n' + + 'Format your response using markdown with clear headers for each rule, code blocks for queries, and concise bullet points for explanations.'; + +export const DATA_QUALITY_ANALYSIS = + 'Explain the ECS incompatibility results above, and describe some options to fix incompatibilities. In your explanation, include information about remapping fields, reindexing data, and modifying data ingestion pipelines. Also, describe how ES|QL can be used to identify and correct incompatible data, including examples of using RENAME, EVAL, DISSECT, GROK, and CASE functions.'; + +export const ALERT_EVALUATION = `Evaluate the security event described above and provide a structured, markdown-formatted summary suitable for inclusion in an Elastic Security case. Ensure you're using all tools available to you. Your response must include: +1. Event Description + - Summarize the event, including user and host risk scores from the provided context. + - Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages. +2. Triage Steps + - List clear, bulleted triage steps tailored to Elastic Security workflows (e.g., alert investigation, timeline creation, entity analytics review). + - Highlight any relevant detection rules or anomaly findings. +3. Recommended Actions + - Provide prioritized response actions, including: + - Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation. + - Example ES|QL queries for further investigation, formatted as code blocks. + - Example OSQuery Manager queries for further investigation, formatted as code blocks. + - Guidance on using Timelines and Entity Analytics for deeper context, with documentation links. +4. MITRE ATT&CK Context + - Summarize the mapped MITRE ATT&CK techniques and provide actionable recommendations based on MITRE guidance, with hyperlinks. +5. Documentation Links + - Include direct links to all referenced Elastic Security documentation and MITRE ATT&CK pages. +Formatting Requirements: + - Use markdown headers, tables, and code blocks for clarity. + - Organize the response into visually distinct sections. + - Use concise, actionable language. + - Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡️, 🔍, 📚). +`; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.test.tsx new file mode 100644 index 0000000000000..ca6c564d329c4 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.test.tsx @@ -0,0 +1,108 @@ +/* + * 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 { renderHook } from '@testing-library/react'; +import type { UseFindPromptContextsParams } from './use_find_prompt_contexts'; +import { useFindPromptContexts } from './use_find_prompt_contexts'; +import { useFindPrompts } from '@kbn/elastic-assistant'; +import { DATA_QUALITY_SUGGESTED_USER_PROMPT } from '@kbn/ecs-data-quality-dashboard'; +import { EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS } from '../../../detection_engine/common/translations'; +import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE } from '../prompts/user/translations'; + +const mockPrompts = [ + { + promptId: 'alertEvaluation', + prompt: 'ALERT EVALUATION', + }, + { + promptId: 'dataQualityAnalysis', + prompt: 'DATA QUALITY ANALYSIS', + }, + { + promptId: 'ruleAnalysis', + prompt: 'RULE ANALYSIS', + }, +]; +jest.mock('@kbn/elastic-assistant'); + +describe('useFindPromptContexts', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockPrompts } }); + }); + + it('calls getPromptContexts with the correct prompts mapped by category', () => { + const params = {} as unknown as UseFindPromptContextsParams; + + renderHook(() => useFindPromptContexts(params)); + + const mockReturn = { + alert: { + category: 'alert', + description: 'Alert (from view)', + suggestedUserPrompt: 'ALERT EVALUATION', + tooltip: 'Add this alert as context', + }, + 'data-quality-dashboard': { + category: 'data-quality-dashboard', + description: 'Data Quality (index)', + suggestedUserPrompt: 'DATA QUALITY ANALYSIS', + tooltip: 'Add this Data Quality report as context', + }, + 'detection-rules': { + category: 'detection-rules', + description: 'Selected Detection Rules', + suggestedUserPrompt: 'RULE ANALYSIS', + tooltip: 'Add this alert as context', + }, + event: { + category: 'event', + description: 'Event (from view)', + suggestedUserPrompt: 'ALERT EVALUATION', + tooltip: 'Add this event as context', + }, + }; + const { result } = renderHook(() => useFindPromptContexts(params)); + expect(result.current).toStrictEqual(mockReturn); + }); + + it('uses correct fallback values when the API does not contain the expected results', () => { + (useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: [] } }); + const params = {} as unknown as UseFindPromptContextsParams; + + renderHook(() => useFindPromptContexts(params)); + + const mockReturn = { + alert: { + category: 'alert', + description: 'Alert (from view)', + suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE, + tooltip: 'Add this alert as context', + }, + 'data-quality-dashboard': { + category: 'data-quality-dashboard', + description: 'Data Quality (index)', + suggestedUserPrompt: DATA_QUALITY_SUGGESTED_USER_PROMPT, + tooltip: 'Add this Data Quality report as context', + }, + 'detection-rules': { + category: 'detection-rules', + description: 'Selected Detection Rules', + suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS, + tooltip: 'Add this alert as context', + }, + event: { + category: 'event', + description: 'Event (from view)', + suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE, + tooltip: 'Add this event as context', + }, + }; + const { result } = renderHook(() => useFindPromptContexts(params)); + expect(result.current).toStrictEqual(mockReturn); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.tsx new file mode 100644 index 0000000000000..aa7774c26869b --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompt_contexts/use_find_prompt_contexts.tsx @@ -0,0 +1,52 @@ +/* + * 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 { useFindPrompts } from '@kbn/elastic-assistant'; +import type { HttpHandler } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import type { FindSecurityAIPromptsRequestQuery } from '@kbn/elastic-assistant-common'; +import { DATA_QUALITY_SUGGESTED_USER_PROMPT } from '@kbn/ecs-data-quality-dashboard'; +import { EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS } from '../../../detection_engine/common/translations'; +import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE } from '../prompts/user/translations'; +import { + DATA_QUALITY_DASHBOARD_CATEGORY, + getPromptContexts, + PROMPT_CONTEXT_ALERT_CATEGORY, + PROMPT_CONTEXT_DETECTION_RULES_CATEGORY, + PROMPT_CONTEXT_EVENT_CATEGORY, +} from '.'; +export interface UseFindPromptContextsParams { + context: { + isAssistantEnabled: boolean; + httpFetch: HttpHandler; + toasts: IToasts; + }; + signal?: AbortSignal | undefined; + params: FindSecurityAIPromptsRequestQuery; +} + +export const useFindPromptContexts = (payload: UseFindPromptContextsParams) => { + const { + data: { prompts }, + } = useFindPrompts(payload); + + const PROMPT_CONTEXTS = getPromptContexts({ + [PROMPT_CONTEXT_ALERT_CATEGORY]: + prompts.find(({ promptId }) => promptId === 'alertEvaluation')?.prompt ?? + EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE, + [PROMPT_CONTEXT_EVENT_CATEGORY]: + prompts.find(({ promptId }) => promptId === 'alertEvaluation')?.prompt ?? + EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE, + [DATA_QUALITY_DASHBOARD_CATEGORY]: + prompts.find(({ promptId }) => promptId === 'dataQualityAnalysis')?.prompt ?? + DATA_QUALITY_SUGGESTED_USER_PROMPT, + [PROMPT_CONTEXT_DETECTION_RULES_CATEGORY]: + prompts.find(({ promptId }) => promptId === 'ruleAnalysis')?.prompt ?? + EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS, + }); + return PROMPT_CONTEXTS; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompts/user/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompts/user/translations.ts index 8892444351832..9c9d50abfa9ac 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompts/user/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/content/prompts/user/translations.ts @@ -7,20 +7,31 @@ import { i18n } from '@kbn/i18n'; -export const THEN_SUMMARIZE_SUGGESTED_KQL_AND_EQL_QUERIES = i18n.translate( - 'xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries', +export const EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE = i18n.translate( + 'xpack.securitySolution.assistant.content.prompts.investigationGuide', { - defaultMessage: - 'Evaluate the event from the context above and format your output neatly in markdown syntax for my Elastic Security case.', + defaultMessage: `Evaluate the security event described above and provide a structured, markdown-formatted summary suitable for inclusion in an Elastic Security case. Ensure you're using all tools available to you. Your response must include: +1. Event Description + - Summarize the event, including user and host risk scores from the provided context. + - Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages. +2. Triage Steps + - List clear, bulleted triage steps tailored to Elastic Security workflows (e.g., alert investigation, timeline creation, entity analytics review). + - Highlight any relevant detection rules or anomaly findings. +3. Recommended Actions + - Provide prioritized response actions, including: + - Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation. + - Example ES|QL queries for further investigation, formatted as code blocks. + - Example OSQuery Manager queries for further investigation, formatted as code blocks. + - Guidance on using Timelines and Entity Analytics for deeper context, with documentation links. +4. MITRE ATT&CK Context + - Summarize the mapped MITRE ATT&CK techniques and provide actionable recommendations based on MITRE guidance, with hyperlinks. +5. Documentation Links + - Include direct links to all referenced Elastic Security documentation and MITRE ATT&CK pages. +Formatting Requirements: + - Use markdown headers, tables, and code blocks for clarity. + - Organize the response into visually distinct sections. + - Use concise, actionable language. + - Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡️, 🔍, 📚). +`, } ); - -export const FINALLY_SUGGEST_INVESTIGATION_GUIDE_AND_FORMAT_AS_MARKDOWN = i18n.translate( - 'xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown', - { - defaultMessage: `Add your description, recommended actions and bulleted triage steps. Use the MITRE ATT&CK data provided to add more context and recommendations from MITRE, and hyperlink to the relevant pages on MITRE\'s website. Be sure to include the user and host risk score data from the context. Your response should include steps that point to Elastic Security specific features, including endpoint response actions, the Elastic Agent OSQuery manager integration (with example osquery queries), timelines and entity analytics and link to all the relevant Elastic Security documentation.`, - } -); - -export const EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N = `${THEN_SUMMARIZE_SUGGESTED_KQL_AND_EQL_QUERIES} -${FINALLY_SUGGEST_INVESTIGATION_GUIDE_AND_FORMAT_AS_MARKDOWN}`; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index 35211ba58c7b1..8a3079a9e5e5a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -24,24 +24,17 @@ import type { HttpSetup } from '@kbn/core-http-browser'; import type { Message } from '@kbn/elastic-assistant-common'; import { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugin/public/common/constants'; import useObservable from 'react-use/lib/useObservable'; -import { useFindPrompts } from '@kbn/elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts'; import { APP_ID } from '../../common'; import { useBasePath, useKibana } from '../common/lib/kibana'; import { useAssistantTelemetry } from './use_assistant_telemetry'; import { getComments } from './get_comments'; import { LOCAL_STORAGE_KEY, augmentMessageCodeBlocks } from './helpers'; import { BASE_SECURITY_QUICK_PROMPTS } from './content/quick_prompts'; -import { - DATA_QUALITY_DASHBOARD_CATEGORY, - getPromptContexts, - PROMPT_CONTEXT_ALERT_CATEGORY, - PROMPT_CONTEXT_DETECTION_RULES_CATEGORY, - PROMPT_CONTEXT_EVENT_CATEGORY, -} from './content/prompt_contexts'; import { useAssistantAvailability } from './use_assistant_availability'; import { useAppToasts } from '../common/hooks/use_app_toasts'; import { useSignalIndex } from '../detections/containers/detection_engine/alerts/use_signal_index'; import { licenseService } from '../common/hooks/use_license'; +import { useFindPromptContexts } from './content/prompt_contexts/use_find_prompt_contexts'; const ASSISTANT_TITLE = i18n.translate('xpack.securitySolution.assistant.title', { defaultMessage: 'Elastic AI Assistant', @@ -226,10 +219,10 @@ export const AssistantProvider: FC> = ({ children }) const alertsIndexPattern = signalIndexName ?? undefined; const toasts = useAppToasts() as unknown as IToasts; // useAppToasts is the current, non-deprecated method of getting the toasts service in the Security Solution, but it doesn't return the IToasts interface (defined by core) - const fetchedPrompts = useFindPrompts({ + const PROMPT_CONTEXTS = useFindPromptContexts({ context: { isAssistantEnabled: assistantAvailability.isAssistantEnabled, - http, + httpFetch: http.fetch, toasts, }, params: { @@ -237,13 +230,6 @@ export const AssistantProvider: FC> = ({ children }) prompt_ids: ['alertEvaluation', 'dataQualityAnalysis', 'ruleAnalysis'], }, }); - console.log('fetchedPrompts', fetchedPrompts); - const PROMPT_CONTEXTS = getPromptContexts({ - [PROMPT_CONTEXT_ALERT_CATEGORY]: 'string', - [PROMPT_CONTEXT_EVENT_CATEGORY]: 'string', - [DATA_QUALITY_DASHBOARD_CATEGORY]: 'string', - [PROMPT_CONTEXT_DETECTION_RULES_CATEGORY]: 'string', - }); return ( openAssistant('rule'); assertNewConversation(false, `Detection Rules - Rule 1`); assertConnectorSelected(azureConnectorAPIPayload.name); - cy.get(USER_PROMPT).should('have.text', EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS); cy.get(PROMPT_CONTEXT_BUTTON(0)).should('have.text', RULE_MANAGEMENT_CONTEXT_DESCRIPTION); }); }); @@ -106,10 +101,6 @@ describe('AI Assistant Conversations', { tags: ['@ess', '@serverless'] }, () => openAssistant('alert'); assertConversationTitleContains('New Rule Test'); assertConnectorSelected(azureConnectorAPIPayload.name); - cy.get(USER_PROMPT).should( - 'have.text', - EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N - ); cy.get(PROMPT_CONTEXT_BUTTON(0)).should('have.text', 'Alert (from summary)'); }); it('Shows empty connector callout when a conversation that had a connector no longer does', () => { @@ -138,7 +129,6 @@ describe('AI Assistant Conversations', { tags: ['@ess', '@serverless'] }, () => assertConversationTitleContains('New Rule Test'); // send message to ensure conversation is created submitMessage(); - assertMessageSent(EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N); closeAssistant(); visitGetStartedPage(); openAssistant(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts index a3420e346e889..94b20dfc5a1b3 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/user/translations'; import { PromptCreateProps } from '@kbn/elastic-assistant-common/impl/schemas'; import { IS_SERVERLESS } from '../../env_var_names_constants'; import { QUICK_PROMPT_BADGE, USER_PROMPT } from '../../screens/ai_assistant'; @@ -182,10 +181,6 @@ describe('AI Assistant Prompts', { tags: ['@ess', '@serverless'] }, () => { expandFirstAlert(); openAssistant('alert'); cy.get(QUICK_PROMPT_BADGE(testPrompt.name)).should('be.visible'); - cy.get(USER_PROMPT).should( - 'have.text', - EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N - ); cy.get(QUICK_PROMPT_BADGE(testPrompt.name)).click(); cy.get(USER_PROMPT).should('have.text', testPrompt.content); }); From 802f51d99db4c57033911560e11b62836ee0b00c Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 12:37:48 -0600 Subject: [PATCH 06/41] rm unused schema --- .../common_attributes.gen.ts | 15 -------------- .../common_attributes.schema.yaml | 20 ------------------- .../find_prompts_route.gen.ts | 2 +- 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts index 50e9ab61b4299..aa9197f2260e1 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen.ts @@ -16,21 +16,6 @@ import { z } from '@kbn/zod'; -/** - * Set of prompt IDs for a specific group of prompts. - */ -export type PromptIdsSet = z.infer; -export const PromptIdsSet = z.object({ - /** - * Array of IDs for a specific prompts - */ - promptIds: z.array(z.string()).min(1), - /** - * The unique identifier for a group of prompts. - */ - promptGroupId: z.string(), -}); - export type PromptItem = z.infer; export const PromptItem = z.object({ promptId: z.string(), diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml index 3835f0279ca62..f6d582512ff3c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.schema.yaml @@ -6,26 +6,6 @@ paths: {} components: x-codegen-enabled: true schemas: - PromptIdsSet: - description: Set of prompt IDs for a specific group of prompts. - type: object - required: - - promptIds - - promptGroupId - properties: - promptIds: - type: array - description: Array of IDs for a specific prompts - minItems: 1 - items: - type: string - example: - - "systemPrompt" - - "userPrompt" - promptGroupId: - type: string - description: The unique identifier for a group of prompts. - example: "aiAssistant" PromptItem: type: object properties: diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts index 55ebadf0e6704..542beef9bb08c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant-common/impl/schemas/security_ai_prompts/find_prompts_route.gen.ts @@ -11,7 +11,7 @@ * * info: * title: Get Security AI Prompt endpoint - * version: 1 + * version: 2023-10-31 */ import { z } from '@kbn/zod'; From 7a2914247628f78502dab74c23d8daea769255dc Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 13:05:13 -0600 Subject: [PATCH 07/41] use assistant update --- .../right/hooks/use_assistant.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts index 4059633c9be61..7a3c137a396f9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.ts @@ -6,7 +6,7 @@ */ import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { useAssistantOverlay } from '@kbn/elastic-assistant'; +import { useAssistantContext, useAssistantOverlay } from '@kbn/elastic-assistant'; import { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; @@ -22,7 +22,6 @@ import { import { PROMPT_CONTEXT_ALERT_CATEGORY, PROMPT_CONTEXT_EVENT_CATEGORY, - PROMPT_CONTEXTS, } from '../../../../assistant/content/prompt_contexts'; const SUMMARY_VIEW = i18n.translate('xpack.securitySolution.eventDetails.summaryView', { @@ -68,6 +67,15 @@ export const useAssistant = ({ isAlert, }: UseAssistantParams): UseAssistantResult => { const { hasAssistantPrivilege, isAssistantEnabled } = useAssistantAvailability(); + const { basePromptContexts } = useAssistantContext(); + const suggestedUserPrompt = useMemo( + () => + basePromptContexts.find( + ({ category }) => + category === (isAlert ? PROMPT_CONTEXT_ALERT_CATEGORY : PROMPT_CONTEXT_EVENT_CATEGORY) + )?.suggestedUserPrompt, + [basePromptContexts, isAlert] + ); const useAssistantHook = hasAssistantPrivilege ? useAssistantOverlay : useAssistantNoop; const getPromptContext = useCallback( async () => getRawData(dataFormattedForFieldBrowser ?? []), @@ -93,9 +101,7 @@ export const useAssistant = ({ : EVENT_SUMMARY_CONTEXT_DESCRIPTION(SUMMARY_VIEW), getPromptContext, null, - isAlert - ? PROMPT_CONTEXTS[PROMPT_CONTEXT_ALERT_CATEGORY].suggestedUserPrompt - : PROMPT_CONTEXTS[PROMPT_CONTEXT_EVENT_CATEGORY].suggestedUserPrompt, + suggestedUserPrompt, isAlert ? ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP : EVENT_SUMMARY_VIEW_CONTEXT_TOOLTIP, isAssistantEnabled ); From 6386908bf15208dba344c30634e39f592cd9b680 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 14:06:26 -0600 Subject: [PATCH 08/41] tests --- .../use_find_prompts.test.tsx | 97 ++++++++++++++++ .../server/__mocks__/request.ts | 10 +- .../security_ai_prompts/find_prompts.test.ts | 106 ++++++++++++++++++ .../right/hooks/use_assistant.test.tsx | 76 +++++++++---- 4 files changed, 264 insertions(+), 25 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.test.tsx create mode 100644 x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.test.tsx new file mode 100644 index 0000000000000..ac6f064d45578 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.test.tsx @@ -0,0 +1,97 @@ +/* + * 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 { waitFor, renderHook } from '@testing-library/react'; +import { useFindPrompts, UseFindPromptsParams } from './use_find_prompts'; +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, +} from '@kbn/elastic-assistant-common'; +import { TestProviders } from '../../../mock/test_providers/test_providers'; +import { IToasts } from '@kbn/core-notifications-browser'; + +const mockHttpFetch = jest.fn(); +const mockToasts = { + addSuccess: jest.fn(), + addError: jest.fn(), +} as unknown as IToasts; + +describe('useFindPrompts', () => { + const params: UseFindPromptsParams = { + context: { + isAssistantEnabled: true, + httpFetch: mockHttpFetch, + toasts: mockToasts, + }, + params: { + connector_id: 'connector-1', + prompt_ids: ['prompt-1'], + prompt_group_id: 'group-1', + }, + signal: undefined, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns initial and placeholder data when loading', async () => { + mockHttpFetch.mockResolvedValueOnce({ prompts: [{ id: 'prompt-1', name: 'Prompt 1' }] }); + const { result } = renderHook(() => useFindPrompts(params), { + wrapper: TestProviders, + }); + expect(result.current.data).toEqual({ prompts: [] }); + await waitFor(() => result.current.isSuccess); + + await waitFor(() => + expect(result.current.data).toEqual({ prompts: [{ id: 'prompt-1', name: 'Prompt 1' }] }) + ); + }); + + it('disables the query if isAssistantEnabled is false', async () => { + const disabledParams = { ...params, context: { ...params.context, isAssistantEnabled: false } }; + const { result } = renderHook(() => useFindPrompts(disabledParams), { wrapper: TestProviders }); + expect(result.current.isLoading).toBe(false); + expect(result.current.data).toEqual({ prompts: [] }); + expect(mockHttpFetch).not.toHaveBeenCalled(); + }); + + it('calls httpFetch with correct arguments', async () => { + mockHttpFetch.mockResolvedValueOnce({ prompts: [{ id: 'prompt-1', name: 'Prompt 1' }] }); + const { result } = renderHook(() => useFindPrompts(params), { + wrapper: TestProviders, + }); + await waitFor(() => result.current.isSuccess); + expect(mockHttpFetch).toHaveBeenCalledWith( + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + expect.objectContaining({ + method: 'GET', + version: API_VERSIONS.public.v1, + query: { + connector_id: 'connector-1', + prompt_ids: ['prompt-1'], + prompt_group_id: 'group-1', + }, + }) + ); + }); + + it('shows a toast on error', async () => { + const error = { body: { message: 'Something went wrong' } }; + mockHttpFetch.mockRejectedValueOnce(error); + const { result } = renderHook(() => useFindPrompts(params), { + wrapper: TestProviders, + }); + await waitFor(() => result.current.isError); + expect(mockToasts.addError).toHaveBeenCalledWith( + expect.any(Error), + expect.objectContaining({ + title: expect.any(String), + }) + ); + }); +}); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts index 14705c3bce78a..1d24605f50cb6 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts @@ -6,11 +6,12 @@ */ import { httpServerMock } from '@kbn/core/server/mocks'; import { CAPABILITIES } from '../../common/constants'; -import type { +import { CreateAttackDiscoverySchedulesRequestBody, DefendInsightsGetRequestQuery, DefendInsightsPostRequestBody, DeleteKnowledgeBaseEntryRequestParams, + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, KnowledgeBaseEntryUpdateProps, UpdateAttackDiscoverySchedulesRequestBody, UpdateKnowledgeBaseEntryRequestParams, @@ -167,6 +168,13 @@ export const getCurrentUserPromptsRequest = () => path: ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND, }); +export const getCurrentUserSecurityAIPromptsRequest = () => + requestMock.create({ + method: 'get', + path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + query: { prompt_group_id: 'aiAssistant', prompt_ids: ['systemPrompt'] }, + }); + export const getCurrentUserAlertSummaryRequest = () => requestMock.create({ method: 'get', diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts new file mode 100644 index 0000000000000..0225180bcf53c --- /dev/null +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts @@ -0,0 +1,106 @@ +/* + * 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 { getCurrentUserSecurityAIPromptsRequest, requestMock } from '../../__mocks__/request'; +import { ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND } from '@kbn/elastic-assistant-common'; +import { serverMock } from '../../__mocks__/server'; +import { requestContextMock } from '../../__mocks__/request_context'; +import { findSecurityAIPromptsRoute } from './find_prompts'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import type { AuthenticatedUser } from '@kbn/core-security-common'; +import { getPromptsByGroupId } from '../../lib/prompt'; +import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; +jest.mock('../../lib/prompt'); +const mockResponse = [{ promptId: 'systemPrompt', prompt: 'This is a prompt' }]; +describe('Find security AI prompts route', () => { + let server: ReturnType; + let { clients, context } = requestContextMock.createTools(); + let logger: ReturnType; + + beforeEach(async () => { + server = serverMock.create(); + ({ clients, context } = requestContextMock.createTools()); + const mockUser1 = { + username: 'my_username', + authentication_realm: { + type: 'my_realm_type', + name: 'my_realm_name', + }, + } as AuthenticatedUser; + + (getPromptsByGroupId as jest.Mock).mockResolvedValue(Promise.resolve(mockResponse)); + context.elasticAssistant.getCurrentUser.mockResolvedValueOnce({ + username: 'my_username', + authentication_realm: { + type: 'my_realm_type', + name: 'my_realm_name', + }, + } as AuthenticatedUser); + (context.elasticAssistant.actions.getActionsClientWithRequest as jest.Mock) = jest + .fn() + .mockReturnValueOnce(actionsClientMock.create()); + logger = loggingSystemMock.createLogger(); + context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1); + findSecurityAIPromptsRoute(server.router, logger); + }); + + describe('status codes', () => { + it('returns 200', async () => { + const response = await server.inject( + getCurrentUserSecurityAIPromptsRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ prompts: mockResponse }); + }); + + it('catches error if search throws error', async () => { + (getPromptsByGroupId as jest.Mock).mockRejectedValueOnce(new Error('Test error')); + const response = await server.inject( + getCurrentUserSecurityAIPromptsRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(500); + expect(response.body).toEqual({ + message: 'Test error', + status_code: 500, + }); + }); + }); + + describe('request validation', () => { + it('allows optional query params', async () => { + const request = requestMock.create({ + method: 'get', + path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + query: { + prompt_group_id: 'aiAssistant', + prompt_ids: ['systemPrompt'], + connector_id: '123', + }, + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + + it('ignores unknown query params', async () => { + const request = requestMock.create({ + method: 'get', + path: ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, + query: { + prompt_group_id: 'aiAssistant', + prompt_ids: ['systemPrompt'], + invalid_value: 'test 1', + }, + }); + const result = server.validate(request); + + expect(result.ok).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx index 9b9d2d59fb6ae..66f16c9280923 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx @@ -10,7 +10,7 @@ import { renderHook } from '@testing-library/react'; import type { UseAssistantParams, UseAssistantResult } from './use_assistant'; import { useAssistant } from './use_assistant'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; -import { useAssistantOverlay } from '@kbn/elastic-assistant'; +import { useAssistantContext, useAssistantOverlay } from '@kbn/elastic-assistant'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; jest.mock('../../../../assistant/use_assistant_availability'); @@ -23,11 +23,11 @@ const renderUseAssistant = () => renderHook((props: UseAssistantParams) => useAssistant(props), { initialProps: { dataFormattedForFieldBrowser, isAlert }, }); +const useAssistantOverlayMock = useAssistantOverlay as jest.Mock; describe('useAssistant', () => { - let hookResult: RenderHookResult; - - it(`should return showAssistant true and a value for promptContextId`, () => { + beforeEach(() => { + jest.clearAllMocks(); jest.mocked(useAssistantAvailability).mockReturnValue({ hasSearchAILakeConfigurations: false, hasAssistantPrivilege: true, @@ -37,12 +37,44 @@ describe('useAssistant', () => { hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }); - jest - .mocked(useAssistantOverlay) - .mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' }); + useAssistantOverlayMock.mockReturnValue({ + showAssistantOverlay: jest.fn, + promptContextId: '123', + }); - hookResult = renderUseAssistant(); + (useAssistantContext as jest.Mock).mockReturnValue({ + basePromptContexts: [ + { + category: 'alert', + description: 'Alert (from view)', + suggestedUserPrompt: 'ALERT EVALUATION', + tooltip: 'Add this alert as context', + }, + { + category: 'data-quality-dashboard', + description: 'Data Quality (index)', + suggestedUserPrompt: 'DATA QUALITY ANALYSIS', + tooltip: 'Add this Data Quality report as context', + }, + { + category: 'detection-rules', + description: 'Selected Detection Rules', + suggestedUserPrompt: 'RULE ANALYSIS', + tooltip: 'Add this alert as context', + }, + { + category: 'event', + description: 'Event (from view)', + suggestedUserPrompt: 'EVENT EVALUATION', + tooltip: 'Add this event as context', + }, + ], + }); + }); + let hookResult: RenderHookResult; + it(`should return showAssistant true and a value for promptContextId`, () => { + hookResult = renderUseAssistant(); expect(hookResult.result.current.showAssistant).toEqual(true); expect(hookResult.result.current.promptContextId).toEqual('123'); }); @@ -57,9 +89,6 @@ describe('useAssistant', () => { hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, }); - jest - .mocked(useAssistantOverlay) - .mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' }); hookResult = renderUseAssistant(); @@ -68,19 +97,6 @@ describe('useAssistant', () => { }); it('returns anonymized prompt context data', async () => { - jest.mocked(useAssistantAvailability).mockReturnValue({ - hasSearchAILakeConfigurations: false, - hasAssistantPrivilege: true, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - hasUpdateAIAssistantAnonymization: true, - hasManageGlobalKnowledgeBase: true, - isAssistantEnabled: true, - }); - jest - .mocked(useAssistantOverlay) - .mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' }); - hookResult = renderUseAssistant(); const getPromptContext = (useAssistantOverlay as jest.Mock).mock.calls[0][3]; @@ -105,4 +121,16 @@ describe('useAssistant', () => { 'user.name': ['user-name'], }); }); + it('returns correct prompt for alert', () => { + renderUseAssistant(); + expect(useAssistantOverlayMock.mock.calls[0][0]).toEqual('alert'); + expect(useAssistantOverlayMock.mock.calls[0][5]).toEqual('ALERT EVALUATION'); + }); + it('returns correct prompt for event', () => { + renderHook((props: UseAssistantParams) => useAssistant(props), { + initialProps: { dataFormattedForFieldBrowser, isAlert: false }, + }); + expect(useAssistantOverlayMock.mock.calls[0][0]).toEqual('event'); + expect(useAssistantOverlayMock.mock.calls[0][5]).toEqual('EVENT EVALUATION'); + }); }); From 542a0d173371fc818dc24ac07012a3a5cc6c2f66 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:30:15 +0000 Subject: [PATCH 09/41] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../impl/data_quality_panel/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts index e249fe3fb567f..9f9e31e9c9142 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/translations.ts @@ -81,7 +81,7 @@ export const DATA_QUALITY_SUGGESTED_USER_PROMPT = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPromptV2', { defaultMessage: - 'Explain the ECS incompatibility results above, and describe some options to fix incompatibilities. In your explanation, include information about remapping fields, reindexing data, and modifying data ingestion pipelines. Also, describe how ES|QL can be used to identify and correct incompatible data, including examples of using RENAME, EVAL, DISSECT, GROK, and CASE functions.' + 'Explain the ECS incompatibility results above, and describe some options to fix incompatibilities. In your explanation, include information about remapping fields, reindexing data, and modifying data ingestion pipelines. Also, describe how ES|QL can be used to identify and correct incompatible data, including examples of using RENAME, EVAL, DISSECT, GROK, and CASE functions.', } ); From 1aad4de325e2663b2ce84e4b2a97408e617c6279 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 14:32:47 -0600 Subject: [PATCH 10/41] fix i18n. --- .../plugins/private/translations/translations/fr-FR.json | 6 +----- .../plugins/private/translations/translations/ja-JP.json | 6 +----- .../plugins/private/translations/translations/zh-CN.json | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 91a69d420e727..1c257e79f67f1 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -7538,7 +7538,6 @@ "securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId": "Tableau de bord de Qualité des données", "securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPill": "Qualité des données ({indexName})", "securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "Ajoutez ce rapport de Qualité des données comme contexte", - "securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "Expliquez les résultats ci-dessus et donnez des options pour résoudre les incompatibilités.", "securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "Vérifier les mappings d'index", "securitySolutionPackages.ecsDataQualityDashboard.detectionEngineRulesWontWorkMessage": "❌ Les règles de moteur de détection référençant ces champs ne leur correspondront peut-être pas correctement", "securitySolutionPackages.ecsDataQualityDashboard.docs": "Documents", @@ -35207,8 +35206,6 @@ "xpack.securitySolution.assistant.commentActions.viewAPMTraceLabel": "Voir la trace APM pour ce message", "xpack.securitySolution.assistant.content.promptContexts.indexTitle": "index", "xpack.securitySolution.assistant.content.promptContexts.viewTitle": "vue", - "xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "Ajoutez votre description, les actions que vous recommandez ainsi que les étapes de triage à puces. Utilisez les données \"MITRE ATT&CK\" fournies pour ajouter du contexte et des recommandations de MITRE ainsi que des liens hypertexte vers les pages pertinentes sur le site web de MITRE. Assurez-vous d’inclure les scores de risque de l’utilisateur et de l’hôte du contexte. Votre réponse doit inclure des étapes qui pointent vers les fonctionnalités spécifiques d’Elastic Security, y compris les actions de réponse du terminal, l’intégration OSQuery Manager d’Elastic Agent (avec des exemples de requêtes OSQuery), des analyses de timeline et d’entités, ainsi qu’un lien pour toute la documentation Elastic Security pertinente.", - "xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "Évaluer l’événement depuis le contexte ci-dessus et formater soigneusement la sortie en syntaxe Markdown pour mon cas Elastic Security.", "xpack.securitySolution.assistant.contentReferences.knowledgeBaseEntryReference.label": "Entrée de la base de connaissances", "xpack.securitySolution.assistant.contentReferences.securityAlertReference.label": "Afficher l'alerte", "xpack.securitySolution.assistant.contentReferences.securityAlertsPageReference.label": "Afficher les alertes", @@ -37139,7 +37136,6 @@ "xpack.securitySolution.detectionEngine.ruleManagement.createRule.threatMappingField.requiredError": "Au moins une correspondance d'indicateur est requise.", "xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesConversationId": "Règles de détection", "xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesCreateEditFormConversationId": "Formulaire de création des règles de détection", - "xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails": "Veuillez expliquer les règles sélectionnées ci-dessus. Pour chaque règle : indiquez pourquoi la règle est pertinente, la requête telle qu'elle est publiée dans le référentiel de règles de détection d'Elastic, ainsi qu'une explication approfondie de celle-ci et ce qu'elle implique généralement pour une organisation si elle est détectée.", "xpack.securitySolution.detectionEngine.ruleManagement.fields.from.helpText": "Expression mathématique de datage, par ex. \"now\", \"now-3d\", \"now+2m\".", "xpack.securitySolution.detectionEngine.ruleManagement.fields.from.label": "De", "xpack.securitySolution.detectionEngine.ruleManagement.fields.interval.helpText": "Les règles s'exécutent de façon régulière et détectent les alertes dans la période de temps spécifiée.", @@ -48677,4 +48673,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "Ce champ est requis.", "xpack.watcher.watcherDescription": "Détectez les modifications survenant dans vos données en créant, gérant et monitorant des alertes." } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index fe31ec553de46..107de8c4a5978 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -7532,7 +7532,6 @@ "securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId": "データ品質ダッシュボード", "securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPill": "データ品質({indexName})", "securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "このデータ品質レポートをコンテキストとして追加", - "securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "上記の結果を説明し、一部のオプションを記述して非互換性を修正します。", "securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "インデックスマッピングの確認", "securitySolutionPackages.ecsDataQualityDashboard.detectionEngineRulesWontWorkMessage": "❌ これらのフィールドを参照する検出エンジンルールが正常に一致しない場合がある", "securitySolutionPackages.ecsDataQualityDashboard.docs": "ドキュメント", @@ -35172,8 +35171,6 @@ "xpack.securitySolution.assistant.commentActions.viewAPMTraceLabel": "このメッセージのAPMトレースを表示", "xpack.securitySolution.assistant.content.promptContexts.indexTitle": "インデックス", "xpack.securitySolution.assistant.content.promptContexts.viewTitle": "表示", - "xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "説明、推奨されるアクション、箇条書きのトリアージステップを追加します。提供された MITRE ATT&CKデータを使用して、MITREからのコンテキストや推奨事項を追加し、MITREのWebサイトの関連ページにハイパーリンクを貼ります。コンテキストのユーザーとホストのリスクスコアデータを必ず含めてください。回答には、エンドポイント対応アクション、ElasticエージェントOSQueryマネージャー統合(osqueryクエリーの例を付けて)、タイムライン、エンティティ分析など、Elasticセキュリティ固有の機能を指す手順を含め、関連するElasticセキュリティのドキュメントすべてにリンクしてください。", - "xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "上記のコンテキストからイベントを評価し、Elasticセキュリティのケース用に、出力をマークダウン構文で正しく書式設定してください。", "xpack.securitySolution.assistant.contentReferences.knowledgeBaseEntryReference.label": "ナレッジベースエントリ", "xpack.securitySolution.assistant.contentReferences.securityAlertReference.label": "アラートを表示", "xpack.securitySolution.assistant.contentReferences.securityAlertsPageReference.label": "アラートを表示", @@ -37101,7 +37098,6 @@ "xpack.securitySolution.detectionEngine.ruleManagement.createRule.threatMappingField.requiredError": "1 つ以上のインジケーター一致が必要です。", "xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesConversationId": "検出ルール", "xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesCreateEditFormConversationId": "検出ルール作成フォーム", - "xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails": "上記の選択したルールを説明してください。各ルールについて、関連する理由、Elasticの検出ルールリポジトリで公開されているクエリとその詳細な説明、検出された場合の組織にとっての一般的な意味を強調します。", "xpack.securitySolution.detectionEngine.ruleManagement.fields.from.helpText": "日付演算式。例:now、now-3d、now+2mなど。", "xpack.securitySolution.detectionEngine.ruleManagement.fields.from.label": "開始:", "xpack.securitySolution.detectionEngine.ruleManagement.fields.interval.helpText": "ルールを定期的に実行し、指定の時間枠内でアラートを検出します。", @@ -48630,4 +48626,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 74bfef5b8f535..b0f18e375da39 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -7543,7 +7543,6 @@ "securitySolutionPackages.ecsDataQualityDashboard.dataQualityDashboardConversationId": "数据质量仪表板", "securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPill": "数据质量 ({indexName})", "securitySolutionPackages.ecsDataQualityDashboard.dataQualityPromptContextPillTooltip": "将此数据质量报告添加为上下文", - "securitySolutionPackages.ecsDataQualityDashboard.dataQualitySuggestedUserPrompt": "解释上述结果,并说明某些选项以解决不兼容问题。", "securitySolutionPackages.ecsDataQualityDashboard.defaultPanelTitle": "检查索引映射", "securitySolutionPackages.ecsDataQualityDashboard.detectionEngineRulesWontWorkMessage": "❌ 引用这些字段的检测引擎规则可能无法与其正确匹配", "securitySolutionPackages.ecsDataQualityDashboard.docs": "文档", @@ -35246,8 +35245,6 @@ "xpack.securitySolution.assistant.commentActions.viewAPMTraceLabel": "查看此消息的 APM 跟踪", "xpack.securitySolution.assistant.content.promptContexts.indexTitle": "索引", "xpack.securitySolution.assistant.content.promptContexts.viewTitle": "视图", - "xpack.securitySolution.assistant.content.prompts.user.finallySuggestInvestigationGuideAndFormatAsMarkdown": "添加描述、建议操作和带项目符号的分类步骤。使用提供的 MITRE ATT&CK 数据以从 MITRE 添加更多上下文和建议,以及指向 MITRE 网站上的相关页面的超链接。确保包括上下文中的用户和主机风险分数数据。您的响应应包含指向 Elastic Security 特定功能的步骤,包括终端响应操作、Elastic 代理 OSQuery 管理器集成(带示例 osquery 查询)、时间线和实体分析,以及所有相关 Elastic Security 文档的链接。", - "xpack.securitySolution.assistant.content.prompts.user.thenSummarizeSuggestedKqlAndEqlQueries": "评估来自上述上下文的事件,并以用于我的 Elastic Security 案例的 Markdown 语法对您的输出进行全面格式化。", "xpack.securitySolution.assistant.contentReferences.knowledgeBaseEntryReference.label": "知识库条目", "xpack.securitySolution.assistant.contentReferences.securityAlertReference.label": "查看告警", "xpack.securitySolution.assistant.contentReferences.securityAlertsPageReference.label": "查看告警", @@ -37178,7 +37175,6 @@ "xpack.securitySolution.detectionEngine.ruleManagement.createRule.threatMappingField.requiredError": "至少需要一个指标匹配。", "xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesConversationId": "检测规则", "xpack.securitySolution.detectionEngine.ruleManagement.detectionRulesCreateEditFormConversationId": "检测规则创建了表单", - "xpack.securitySolution.detectionEngine.ruleManagement.explainThenSummarizeRuleDetails": "请解释上面的选定规则。对于每个规则,重点说明它们为什么相关、在 Elastic 的检测规则存储库中发布的查询并提供深入解释,以及它们通常对组织的影响(如果检测到)。", "xpack.securitySolution.detectionEngine.ruleManagement.fields.from.helpText": "日期数学表达式,如“now”、“now-3d”、“now+2m”。", "xpack.securitySolution.detectionEngine.ruleManagement.fields.from.label": "自", "xpack.securitySolution.detectionEngine.ruleManagement.fields.interval.helpText": "规则定期运行并检测指定时间范围内的告警。", @@ -48720,4 +48716,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} From aec1a6ab5b70adce03342e24671fdff2301d11b8 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 15:00:49 -0600 Subject: [PATCH 11/41] fix import --- .../plugins/elastic_assistant/server/__mocks__/request.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts index 1d24605f50cb6..9103c5b8260a3 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/__mocks__/request.ts @@ -6,12 +6,11 @@ */ import { httpServerMock } from '@kbn/core/server/mocks'; import { CAPABILITIES } from '../../common/constants'; -import { +import type { CreateAttackDiscoverySchedulesRequestBody, DefendInsightsGetRequestQuery, DefendInsightsPostRequestBody, DeleteKnowledgeBaseEntryRequestParams, - ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, KnowledgeBaseEntryUpdateProps, UpdateAttackDiscoverySchedulesRequestBody, UpdateKnowledgeBaseEntryRequestParams, @@ -50,6 +49,7 @@ import { ELASTIC_AI_ASSISTANT_PROMPTS_URL_FIND, PerformKnowledgeBaseEntryBulkActionRequestBody, PostEvaluateRequestBodyInput, + ELASTIC_AI_ASSISTANT_SECURITY_AI_PROMPTS_URL_FIND, } from '@kbn/elastic-assistant-common'; import { getAppendConversationMessagesSchemaMock, From c062a77e8a2e5a1dd128cdb0b5237bf99d312924 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 15:27:50 -0600 Subject: [PATCH 12/41] wip --- .../assistant/assistant_body/empty_convo.tsx | 18 ++++- .../impl/assistant/assistant_body/index.tsx | 2 +- .../assistant_body/starter_prompts.tsx | 78 +++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 86bd31318349c..1c16e68e8d49e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; import { AssistantBeacon } from '@kbn/ai-assistant-icon'; +import { StarterPrompts } from './starter_prompts'; import { SystemPrompt } from '../prompt_editor/system_prompt'; import { SetupKnowledgeBaseButton } from '../../knowledge_base/setup_knowledge_base_button'; import * as i18n from '../translations'; @@ -21,6 +22,9 @@ interface Props { setCurrentSystemPromptId: (promptId: string | undefined) => void; allSystemPrompts: PromptResponse[]; } +const starterPromptWrapperClassName = css` + max-width: 95%; +`; export const EmptyConvo: React.FC = ({ allSystemPrompts, @@ -30,7 +34,12 @@ export const EmptyConvo: React.FC = ({ setIsSettingsModalVisible, }) => { return ( - + = ({ + + { + console.log('prompt', p); + }} + /> + ); }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx index f93b62bbe67eb..7330dc855127c 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx @@ -109,7 +109,7 @@ export const AssistantBody: FunctionComponent = ({ return ( - + {isLoading ? ( void; +} +const starterPromptClassName = css` + max-width: 50%; + min-width: calc(50% - 8px); +`; + +const starterPromptInnerClassName = css` + text-align: center !important; +`; + +export const StarterPrompts: React.FC = ({ onSelectPrompt }) => { + const starterPrompts = [ + { + prompt: 'What is the status of my last deployment?', + title: 'Deployment Status', + icon: 'sparkles', + }, + { + prompt: 'How many users logged in last week?', + title: 'User Activity', + icon: 'bell', + }, + { + prompt: 'What are the top errors in the logs?', + title: 'Log Analysis', + icon: 'bullseye', + }, + { + prompt: 'Can you summarize the latest sales report?', + title: 'Sales Summary', + icon: 'questionInCircle', + }, + ]; + return ( + + {starterPrompts.map(({ prompt, title, icon }) => ( + + onSelectPrompt(prompt)} + className={starterPromptInnerClassName} + > + + + + +

{title}

+
+ {prompt} +
+
+ ))} +
+ ); +}; From ac7da86685677454ee4d9a1c8c047815507e700e Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 15:28:35 -0600 Subject: [PATCH 13/41] fix type --- .../server/routes/security_ai_prompts/find_prompts.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts index 0225180bcf53c..1942aaf03fd2b 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/routes/security_ai_prompts/find_prompts.test.ts @@ -18,12 +18,12 @@ jest.mock('../../lib/prompt'); const mockResponse = [{ promptId: 'systemPrompt', prompt: 'This is a prompt' }]; describe('Find security AI prompts route', () => { let server: ReturnType; - let { clients, context } = requestContextMock.createTools(); + let { context } = requestContextMock.createTools(); let logger: ReturnType; beforeEach(async () => { server = serverMock.create(); - ({ clients, context } = requestContextMock.createTools()); + ({ context } = requestContextMock.createTools()); const mockUser1 = { username: 'my_username', authentication_realm: { From d7e209885c5b26bf15bd22665a10b11979451cc7 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 15:33:34 -0600 Subject: [PATCH 14/41] more wip --- .../impl/assistant/assistant_body/starter_prompts.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx index b842328054f4b..f04e25dca7e09 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx @@ -31,16 +31,16 @@ const starterPromptInnerClassName = css` export const StarterPrompts: React.FC = ({ onSelectPrompt }) => { const starterPrompts = [ + { + prompt: 'Show me the important alerts from the last 24 hours', + title: 'User Activity', + icon: 'bell', + }, { prompt: 'What is the status of my last deployment?', title: 'Deployment Status', icon: 'sparkles', }, - { - prompt: 'How many users logged in last week?', - title: 'User Activity', - icon: 'bell', - }, { prompt: 'What are the top errors in the logs?', title: 'Log Analysis', From 82649427a5cf4eb392d67a3f3d0b1740257c9574 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 16:02:21 -0600 Subject: [PATCH 15/41] ui works! --- .../assistant/assistant_body/empty_convo.tsx | 8 ++--- .../impl/assistant/assistant_body/index.tsx | 3 ++ .../assistant_body/starter_prompts.tsx | 31 +++++++++++++------ .../impl/assistant/index.tsx | 1 + 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 1c16e68e8d49e..37e0ec416ffa7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -21,6 +21,7 @@ interface Props { setIsSettingsModalVisible: Dispatch>; setCurrentSystemPromptId: (promptId: string | undefined) => void; allSystemPrompts: PromptResponse[]; + setUserPrompt: React.Dispatch>; } const starterPromptWrapperClassName = css` max-width: 95%; @@ -32,6 +33,7 @@ export const EmptyConvo: React.FC = ({ isSettingsModalVisible, setCurrentSystemPromptId, setIsSettingsModalVisible, + setUserPrompt, }) => { return ( = ({
- { - console.log('prompt', p); - }} - /> +
); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx index 7330dc855127c..2f2bcef847b5e 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx @@ -43,6 +43,7 @@ interface Props { http: HttpSetup; setCurrentSystemPromptId: (promptId: string | undefined) => void; setIsSettingsModalVisible: Dispatch>; + setUserPrompt: React.Dispatch>; } export const AssistantBody: FunctionComponent = ({ @@ -58,6 +59,7 @@ export const AssistantBody: FunctionComponent = ({ isSettingsModalVisible, isWelcomeSetup, setIsSettingsModalVisible, + setUserPrompt, }) => { const { euiTheme } = useEuiTheme(); @@ -127,6 +129,7 @@ export const AssistantBody: FunctionComponent = ({ isSettingsModalVisible={isSettingsModalVisible} setCurrentSystemPromptId={setCurrentSystemPromptId} setIsSettingsModalVisible={setIsSettingsModalVisible} + setUserPrompt={setUserPrompt} /> ) : ( void; + setUserPrompt: React.Dispatch>; } const starterPromptClassName = css` max-width: 50%; @@ -29,33 +29,44 @@ const starterPromptInnerClassName = css` text-align: center !important; `; -export const StarterPrompts: React.FC = ({ onSelectPrompt }) => { +export const StarterPrompts: React.FC = ({ setUserPrompt }) => { const starterPrompts = [ { - prompt: 'Show me the important alerts from the last 24 hours', + description: 'Show me the important alerts from the last 24 hours', title: 'User Activity', icon: 'bell', + prompt: 'big long prompt 1', }, { - prompt: 'What is the status of my last deployment?', + description: 'What is the status of my last deployment?', title: 'Deployment Status', icon: 'sparkles', + prompt: 'big long prompt 2', }, { - prompt: 'What are the top errors in the logs?', + description: 'What are the top errors in the logs?', title: 'Log Analysis', icon: 'bullseye', + prompt: 'big long prompt 3', }, { - prompt: 'Can you summarize the latest sales report?', + description: 'Can you summarize the latest sales report?', title: 'Sales Summary', icon: 'questionInCircle', + prompt: 'big long prompt 4', }, ]; + + const onSelectPrompt = useCallback( + (prompt: string) => { + setUserPrompt(prompt); + }, + [setUserPrompt] + ); return ( - {starterPrompts.map(({ prompt, title, icon }) => ( - + {starterPrompts.map(({ description, title, icon, prompt }) => ( + = ({ onSelectPrompt }) => {

{title}

- {prompt} + {description}
))} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx index dc9ad85039906..a3dd19ff7f4b7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/index.tsx @@ -562,6 +562,7 @@ const AssistantComponent: React.FC = ({ isLoading={isInitialLoad} isSettingsModalVisible={isSettingsModalVisible} isWelcomeSetup={isWelcomeSetup} + setUserPrompt={setUserPrompt} setCurrentSystemPromptId={setCurrentSystemPromptId} setIsSettingsModalVisible={setIsSettingsModalVisible} /> From add9ccfb9bed671734690844da6fe5098a3c2665 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 17:33:50 -0600 Subject: [PATCH 16/41] ui/api done --- .../api/prompts/use_fetch_prompts.test.tsx | 13 +- .../security_ai_prompts/use_find_prompts.ts | 4 +- .../assistant/assistant_body/empty_convo.tsx | 1 + .../assistant_body/starter_prompts.test.tsx | 161 ++++++++++++++++++ .../assistant_body/starter_prompts.tsx | 110 +++++++++--- .../server/lib/prompt/local_prompt_object.ts | 103 ++++++++++- .../tools/security_labs/security_labs_tool.ts | 4 + 7 files changed, 358 insertions(+), 38 deletions(-) create mode 100644 x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.test.tsx diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx index 8fb1f808f8df5..fb934afcee437 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx @@ -11,14 +11,15 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { ReactNode } from 'react'; import React from 'react'; import { useFetchPrompts } from './use_fetch_prompts'; -import { HttpSetup } from '@kbn/core-http-browser'; import { useAssistantContext } from '../../../assistant_context'; -import { API_VERSIONS, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; - -const http = { - fetch: jest.fn().mockResolvedValue(defaultAssistantFeatures), -} as unknown as HttpSetup; +import { API_VERSIONS } from '@kbn/elastic-assistant-common'; +(useAssistantContext as jest.Mock).mockReturnValue({ + http, + assistantAvailability: { + isAssistantEnabled: true, + }, +}); jest.mock('../../../assistant_context'); const createWrapper = () => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts index 00a93c99a2511..67a3604140b43 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts @@ -19,7 +19,7 @@ export interface UseFindPromptsParams { context: { isAssistantEnabled: boolean; httpFetch: HttpHandler; - toasts: IToasts; + toasts?: IToasts; }; signal?: AbortSignal | undefined; params: FindSecurityAIPromptsRequestQuery; @@ -81,7 +81,7 @@ const getPrompts = async ({ query, }: { httpFetch: HttpHandler; - toasts: IToasts; + toasts?: IToasts; signal?: AbortSignal | undefined; query: FindSecurityAIPromptsRequestQuery; }) => { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 37e0ec416ffa7..4c0e74de69ac7 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -75,6 +75,7 @@ export const EmptyConvo: React.FC = ({
+ {/* TODO feature flag */} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.test.tsx new file mode 100644 index 0000000000000..57064176c0417 --- /dev/null +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.test.tsx @@ -0,0 +1,161 @@ +/* + * 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 { + formatPromptGroups, + getAllPromptIds, + promptGroups, + StarterPrompts, +} from './starter_prompts'; +import { fireEvent, render } from '@testing-library/react'; +import { TestProviders } from '../../mock/test_providers/test_providers'; +import React from 'react'; +import { useFindPrompts } from '../../..'; +const mockResponse = [ + { + promptId: 'starterPromptTitle1', + prompt: 'starterPromptTitle1 from API yall', + }, + { + promptId: 'starterPromptDescription1', + prompt: 'starterPromptDescription1 from API yall', + }, + { + promptId: 'starterPromptIcon1', + prompt: 'starterPromptIcon1 from API yall', + }, + { + promptId: 'starterPromptPrompt1', + prompt: 'starterPromptPrompt1 from API yall', + }, + { + promptId: 'starterPromptDescription2', + prompt: 'starterPromptDescription2 from API yall', + }, + { + promptId: 'starterPromptTitle2', + prompt: 'starterPromptTitle2 from API yall', + }, + { + promptId: 'starterPromptIcon2', + prompt: 'starterPromptIcon2 from API yall', + }, + { + promptId: 'starterPromptPrompt2', + prompt: 'starterPromptPrompt2 from API yall', + }, + { + promptId: 'starterPromptDescription3', + prompt: 'starterPromptDescription3 from API yall', + }, + { + promptId: 'starterPromptTitle3', + prompt: 'starterPromptTitle3 from API yall', + }, + { + promptId: 'starterPromptIcon3', + prompt: 'starterPromptIcon3 from API yall', + }, + { + promptId: 'starterPromptPrompt3', + prompt: 'starterPromptPrompt3 from API yall', + }, + { + promptId: 'starterPromptDescription4', + prompt: 'starterPromptDescription4 from API yall', + }, + { + promptId: 'starterPromptTitle4', + prompt: 'starterPromptTitle4 from API yall', + }, + { + promptId: 'starterPromptPrompt4', + prompt: 'starterPromptPrompt4 from API yall', + }, +]; + +const testProps = { + setUserPrompt: jest.fn(), +}; + +jest.mock('../../..', () => { + return { + useFindPrompts: jest.fn(), + useAssistantContext: () => ({ + assistantAvailability: { + isAssistantEnabled: true, + }, + http: { fetch: {} }, + }), + }; +}); + +describe('StarterPrompts', () => { + it('should return an empty array if no prompts are provided', () => { + expect(getAllPromptIds(promptGroups)).toEqual([ + 'starterPromptTitle1', + 'starterPromptDescription1', + 'starterPromptIcon1', + 'starterPromptPrompt1', + 'starterPromptDescription2', + 'starterPromptTitle2', + 'starterPromptIcon2', + 'starterPromptPrompt2', + 'starterPromptDescription3', + 'starterPromptTitle3', + 'starterPromptIcon3', + 'starterPromptPrompt3', + 'starterPromptDescription4', + 'starterPromptTitle4', + 'starterPromptIcon4', + 'starterPromptPrompt4', + ]); + }); + it('should return the correct prompt groups with fetched prompts', () => { + const response = formatPromptGroups(mockResponse); + expect(response).toEqual([ + { + description: 'starterPromptDescription1 from API yall', + icon: 'starterPromptIcon1 from API yall', + prompt: 'starterPromptPrompt1 from API yall', + title: 'starterPromptTitle1 from API yall', + }, + { + description: 'starterPromptDescription2 from API yall', + icon: 'starterPromptIcon2 from API yall', + prompt: 'starterPromptPrompt2 from API yall', + title: 'starterPromptTitle2 from API yall', + }, + { + description: 'starterPromptDescription3 from API yall', + icon: 'starterPromptIcon3 from API yall', + prompt: 'starterPromptPrompt3 from API yall', + title: 'starterPromptTitle3 from API yall', + }, + // starterPrompt Group4 should not exist because starterPromptIcon4 is not in the mockResponse + ]); + }); + it('the component renders correctly with valid props', () => { + (useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockResponse } }); + const { getByTestId } = render( + + + + ); + expect(getByTestId('starterPromptPrompt2 from API yall')).toBeInTheDocument(); + }); + it('calls setUserPrompt when a prompt is selected', () => { + (useFindPrompts as jest.Mock).mockReturnValue({ data: { prompts: mockResponse } }); + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('starterPromptPrompt2 from API yall')); + expect(testProps.setUserPrompt).toHaveBeenCalledWith('starterPromptPrompt2 from API yall'); + }); +}); diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx index f9c0bbd1284df..dd05a42ad3284 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/starter_prompts.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -16,6 +16,8 @@ import { EuiTitle, } from '@elastic/eui'; import { css } from '@emotion/css'; +import { PromptItemArray } from '@kbn/elastic-assistant-common/impl/schemas/security_ai_prompts/common_attributes.gen'; +import { useAssistantContext, useFindPrompts } from '../../..'; interface Props { setUserPrompt: React.Dispatch>; @@ -28,34 +30,66 @@ const starterPromptClassName = css` const starterPromptInnerClassName = css` text-align: center !important; `; +interface PromptGroup { + description: string; + title: string; + icon: string; + prompt: string; +} +// these are the promptIds (Security AI Prompts integration) for each of the starter prompts fields +export const promptGroups = [ + { + title: 'starterPromptTitle1', + description: 'starterPromptDescription1', + icon: 'starterPromptIcon1', + prompt: 'starterPromptPrompt1', + }, + { + description: 'starterPromptDescription2', + title: 'starterPromptTitle2', + icon: 'starterPromptIcon2', + prompt: 'starterPromptPrompt2', + }, + { + description: 'starterPromptDescription3', + title: 'starterPromptTitle3', + icon: 'starterPromptIcon3', + prompt: 'starterPromptPrompt3', + }, + { + description: 'starterPromptDescription4', + title: 'starterPromptTitle4', + icon: 'starterPromptIcon4', + prompt: 'starterPromptPrompt4', + }, +]; export const StarterPrompts: React.FC = ({ setUserPrompt }) => { - const starterPrompts = [ - { - description: 'Show me the important alerts from the last 24 hours', - title: 'User Activity', - icon: 'bell', - prompt: 'big long prompt 1', - }, - { - description: 'What is the status of my last deployment?', - title: 'Deployment Status', - icon: 'sparkles', - prompt: 'big long prompt 2', + const { + assistantAvailability: { isAssistantEnabled }, + http, + toasts, + } = useAssistantContext(); + const { + data: { prompts: actualPrompts }, + } = useFindPrompts({ + context: { + isAssistantEnabled, + httpFetch: http.fetch, + toasts, }, - { - description: 'What are the top errors in the logs?', - title: 'Log Analysis', - icon: 'bullseye', - prompt: 'big long prompt 3', + params: { + prompt_group_id: 'aiAssistant', + prompt_ids: getAllPromptIds(promptGroups), }, - { - description: 'Can you summarize the latest sales report?', - title: 'Sales Summary', - icon: 'questionInCircle', - prompt: 'big long prompt 4', - }, - ]; + }); + + const fetchedPromptGroups = useMemo(() => { + if (!actualPrompts.length) { + return []; + } + return formatPromptGroups(actualPrompts); + }, [actualPrompts]); const onSelectPrompt = useCallback( (prompt: string) => { @@ -63,14 +97,16 @@ export const StarterPrompts: React.FC = ({ setUserPrompt }) => { }, [setUserPrompt] ); + return ( - {starterPrompts.map(({ description, title, icon, prompt }) => ( - + {fetchedPromptGroups.map(({ description, title, icon, prompt }) => ( + onSelectPrompt(prompt)} className={starterPromptInnerClassName} > @@ -87,3 +123,23 @@ export const StarterPrompts: React.FC = ({ setUserPrompt }) => { ); }; + +export const getAllPromptIds = (pGroups: PromptGroup[]) => { + return pGroups.map((promptGroup: PromptGroup) => [...Object.values(promptGroup)]).flat(); +}; + +export const formatPromptGroups = (actualPrompts: PromptItemArray): PromptGroup[] => + promptGroups.reduce((acc, promptGroup) => { + const foundPrompt = (field: keyof PromptGroup) => + actualPrompts.find((p) => p.promptId === promptGroup[field])?.prompt; + const toBePrompt = { + prompt: foundPrompt('prompt'), + icon: foundPrompt('icon'), + title: foundPrompt('title'), + description: foundPrompt('description'), + }; + if (toBePrompt.prompt && toBePrompt.icon && toBePrompt.title && toBePrompt.description) { + acc.push(toBePrompt as PromptGroup); + } + return acc; + }, []); diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts index 703e29c8f30a4..2ba27a46a1a65 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts @@ -41,9 +41,6 @@ export const promptGroupId = { }; export const promptDictionary = { - alertEvaluation: `alertEvaluation`, - dataQualityAnalysis: 'dataQualityAnalysis', - ruleAnalysis: 'ruleAnalysis', alertSummary: `alertSummary`, alertSummarySystemPrompt: `alertSummarySystemPrompt`, systemPrompt: `systemPrompt`, @@ -68,6 +65,26 @@ export const promptDictionary = { 'defendInsights-incompatibleAntivirusEventsEndpointId', defendInsightsIncompatibleAntivirusEventsValue: 'defendInsights-incompatibleAntivirusEventsValue', // context prompts + alertEvaluation: `alertEvaluation`, + dataQualityAnalysis: 'dataQualityAnalysis', + ruleAnalysis: 'ruleAnalysis', + // starter prompts + starterPromptDescription1: 'starterPromptDescription1', + starterPromptTitle1: 'starterPromptTitle1', + starterPromptIcon1: 'starterPromptIcon1', + starterPromptPrompt1: 'starterPromptPrompt1', + starterPromptDescription2: 'starterPromptDescription2', + starterPromptTitle2: 'starterPromptTitle2', + starterPromptIcon2: 'starterPromptIcon2', + starterPromptPrompt2: 'starterPromptPrompt2', + starterPromptDescription3: 'starterPromptDescription3', + starterPromptTitle3: 'starterPromptTitle3', + starterPromptIcon3: 'starterPromptIcon3', + starterPromptPrompt3: 'starterPromptPrompt3', + starterPromptDescription4: 'starterPromptDescription4', + starterPromptTitle4: 'starterPromptTitle4', + starterPromptIcon4: 'starterPromptIcon4', + starterPromptPrompt4: 'starterPromptPrompt4', }; export const localPrompts: Prompt[] = [ @@ -287,4 +304,84 @@ export const localPrompts: Prompt[] = [ default: RULE_ANALYSIS, }, }, + { + promptId: promptDictionary.starterPromptDescription1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptDescription1 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptTitle1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptTitle1 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptIcon1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptIcon1 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptPrompt1, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptPrompt1 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptDescription2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptDescription2 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptTitle2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptTitle2 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptIcon2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptIcon2 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptPrompt2, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptPrompt2 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptDescription3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptDescription3 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptTitle3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptTitle3 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptIcon3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptIcon3 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptPrompt3, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptPrompt3 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptDescription4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptDescription4 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptTitle4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptTitle4 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptIcon4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptIcon4 from API yall' }, + }, + { + promptId: promptDictionary.starterPromptPrompt4, + promptGroupId: promptGroupId.aiAssistant, + prompt: { default: 'starterPromptPrompt4 from API yall' }, + }, ]; diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index d9f4f75cd7716..7ec1386a10ad5 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -51,6 +51,10 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { kbResource: SECURITY_LABS_RESOURCE, query: input.question, }); + if (docs.length === 0) { + // TODO add KB install check here + return 'No relevant Elastic Security Labs content found for the provided query. Ensure knowledge base is installed.'; + } const citedDocs = docs.map((doc) => { let reference: ContentReference | undefined; From 057cb17a62eec18dda2299aee050173f691a09ec Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 17:46:36 -0600 Subject: [PATCH 17/41] add feature flag --- .../assistant/assistant_body/empty_convo.tsx | 11 +++++--- .../impl/assistant_context/types.tsx | 2 ++ .../common/experimental_features.ts | 4 +++ .../use_assistant_availability/index.tsx | 26 ++++++------------- .../tools/security_labs/security_labs_tool.ts | 4 +-- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 4c0e74de69ac7..89965f52a9333 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { css } from '@emotion/react'; import { PromptResponse } from '@kbn/elastic-assistant-common'; import { AssistantBeacon } from '@kbn/ai-assistant-icon'; +import { useAssistantContext } from '../../..'; import { StarterPrompts } from './starter_prompts'; import { SystemPrompt } from '../prompt_editor/system_prompt'; import { SetupKnowledgeBaseButton } from '../../knowledge_base/setup_knowledge_base_button'; @@ -35,6 +36,7 @@ export const EmptyConvo: React.FC = ({ setIsSettingsModalVisible, setUserPrompt, }) => { + const { assistantAvailability } = useAssistantContext(); return ( = ({ - {/* TODO feature flag */} - - - + {assistantAvailability.isStarterPromptsEnabled && ( + + + + )} ); }; diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index 3c8ecbb2a70dc..ce024d7d02178 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -74,6 +74,8 @@ export interface AssistantAvailability { hasUpdateAIAssistantAnonymization: boolean; // When true, user has `Edit` privilege for `Global Knowledge Base` hasManageGlobalKnowledgeBase: boolean; + // remove once product has signed off on prompt text + isStarterPromptsEnabled: boolean; } export type GetAssistantMessages = (commentArgs: { diff --git a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts index fe86441f86cd9..a5e7c9a8fcf72 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts @@ -279,6 +279,10 @@ export const allowedExperimentalValues = Object.freeze({ * Enables advanced mode for Trusted Apps creation and update */ trustedAppsAdvancedMode: false, + /** + * Enables the starter prompts in the Elastic Assistant + */ + starterPromptsEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx index e08aa8579c2ae..a88850db20948 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx @@ -5,28 +5,13 @@ * 2.0. */ +import type { AssistantAvailability } from '@kbn/elastic-assistant'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { useLicense } from '../../common/hooks/use_license'; import { useKibana } from '../../common/lib/kibana'; import { ASSISTANT_FEATURE_ID, SECURITY_FEATURE_ID } from '../../../common/constants'; -export interface UseAssistantAvailability { - // True when searchAiLake configurations is available - hasSearchAILakeConfigurations: boolean; - // True when user is Enterprise. When false, the Assistant is disabled and unavailable - isAssistantEnabled: boolean; - // When true, the Assistant is hidden and unavailable - hasAssistantPrivilege: boolean; - // When true, user has `All` privilege for `Connectors and Actions` (show/execute/delete/save ui capabilities) - hasConnectorsAllPrivilege: boolean; - // When true, user has `Read` privilege for `Connectors and Actions` (show/execute ui capabilities) - hasConnectorsReadPrivilege: boolean; - // When true, user has `Edit` privilege for `AnonymizationFields` - hasUpdateAIAssistantAnonymization: boolean; - // When true, user has `Edit` privilege for `Global Knowledge Base` - hasManageGlobalKnowledgeBase: boolean; -} - -export const useAssistantAvailability = (): UseAssistantAvailability => { +export const useAssistantAvailability = (): AssistantAvailability => { const isEnterprise = useLicense().isEnterprise(); const capabilities = useKibana().services.application.capabilities; const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true; @@ -46,12 +31,17 @@ export const useAssistantAvailability = (): UseAssistantAvailability => { capabilities.actions?.delete === true && capabilities.actions?.save === true; + const starterPromptsEnabled = useIsExperimentalFeatureEnabled('starterPromptsEnabled'); + // remove once product has signed off on prompt text + const isStarterPromptsEnabled = starterPromptsEnabled; + return { hasSearchAILakeConfigurations, hasAssistantPrivilege, hasConnectorsAllPrivilege, hasConnectorsReadPrivilege, isAssistantEnabled: isEnterprise, + isStarterPromptsEnabled, hasUpdateAIAssistantAnonymization, hasManageGlobalKnowledgeBase, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index 7ec1386a10ad5..a543bd6dddd42 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -51,8 +51,8 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { kbResource: SECURITY_LABS_RESOURCE, query: input.question, }); - if (docs.length === 0) { - // TODO add KB install check here + if (docs.length === 0 && !params.isEnabledKnowledgeBase) { + // prompt to help user install knowledge base return 'No relevant Elastic Security Labs content found for the provided query. Ensure knowledge base is installed.'; } From 2b9942601180910f45955c9fe1b07d491a4018cc Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 23 Jun 2025 18:19:45 -0600 Subject: [PATCH 18/41] fix isKnowledgeBaseInstalled --- .../api/prompts/use_fetch_prompts.test.tsx | 13 ++-- .../server/lib/prompt/local_prompt_object.ts | 48 +++++++++----- .../server/lib/prompt/prompts.ts | 64 +++++++++++++++++++ .../tools/security_labs/security_labs_tool.ts | 7 +- 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx index fb934afcee437..8fb1f808f8df5 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/prompts/use_fetch_prompts.test.tsx @@ -11,15 +11,14 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { ReactNode } from 'react'; import React from 'react'; import { useFetchPrompts } from './use_fetch_prompts'; +import { HttpSetup } from '@kbn/core-http-browser'; import { useAssistantContext } from '../../../assistant_context'; -import { API_VERSIONS } from '@kbn/elastic-assistant-common'; +import { API_VERSIONS, defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; + +const http = { + fetch: jest.fn().mockResolvedValue(defaultAssistantFeatures), +} as unknown as HttpSetup; -(useAssistantContext as jest.Mock).mockReturnValue({ - http, - assistantAvailability: { - isAssistantEnabled: true, - }, -}); jest.mock('../../../assistant_context'); const createWrapper = () => { diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts index 2ba27a46a1a65..4e5e86ea73368 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/local_prompt_object.ts @@ -29,6 +29,22 @@ import { RULE_ANALYSIS, DATA_QUALITY_ANALYSIS, ALERT_EVALUATION, + starterPromptTitle1, + starterPromptDescription1, + starterPromptIcon1, + starterPromptPrompt1, + starterPromptDescription2, + starterPromptTitle2, + starterPromptIcon2, + starterPromptPrompt2, + starterPromptDescription3, + starterPromptTitle3, + starterPromptIcon3, + starterPromptPrompt3, + starterPromptDescription4, + starterPromptTitle4, + starterPromptIcon4, + starterPromptPrompt4, } from './prompts'; export const promptGroupId = { @@ -307,81 +323,81 @@ export const localPrompts: Prompt[] = [ { promptId: promptDictionary.starterPromptDescription1, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptDescription1 from API yall' }, + prompt: { default: starterPromptDescription1 }, }, { promptId: promptDictionary.starterPromptTitle1, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptTitle1 from API yall' }, + prompt: { default: starterPromptTitle1 }, }, { promptId: promptDictionary.starterPromptIcon1, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptIcon1 from API yall' }, + prompt: { default: starterPromptIcon1 }, }, { promptId: promptDictionary.starterPromptPrompt1, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptPrompt1 from API yall' }, + prompt: { default: starterPromptPrompt1 }, }, { promptId: promptDictionary.starterPromptDescription2, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptDescription2 from API yall' }, + prompt: { default: starterPromptDescription2 }, }, { promptId: promptDictionary.starterPromptTitle2, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptTitle2 from API yall' }, + prompt: { default: starterPromptTitle2 }, }, { promptId: promptDictionary.starterPromptIcon2, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptIcon2 from API yall' }, + prompt: { default: starterPromptIcon2 }, }, { promptId: promptDictionary.starterPromptPrompt2, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptPrompt2 from API yall' }, + prompt: { default: starterPromptPrompt2 }, }, { promptId: promptDictionary.starterPromptDescription3, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptDescription3 from API yall' }, + prompt: { default: starterPromptDescription3 }, }, { promptId: promptDictionary.starterPromptTitle3, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptTitle3 from API yall' }, + prompt: { default: starterPromptTitle3 }, }, { promptId: promptDictionary.starterPromptIcon3, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptIcon3 from API yall' }, + prompt: { default: starterPromptIcon3 }, }, { promptId: promptDictionary.starterPromptPrompt3, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptPrompt3 from API yall' }, + prompt: { default: starterPromptPrompt3 }, }, { promptId: promptDictionary.starterPromptDescription4, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptDescription4 from API yall' }, + prompt: { default: starterPromptDescription4 }, }, { promptId: promptDictionary.starterPromptTitle4, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptTitle4 from API yall' }, + prompt: { default: starterPromptTitle4 }, }, { promptId: promptDictionary.starterPromptIcon4, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptIcon4 from API yall' }, + prompt: { default: starterPromptIcon4 }, }, { promptId: promptDictionary.starterPromptPrompt4, promptGroupId: promptGroupId.aiAssistant, - prompt: { default: 'starterPromptPrompt4 from API yall' }, + prompt: { default: starterPromptPrompt4 }, }, ]; diff --git a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts index 94ac4c2af1579..bf8b30a6b884a 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/server/lib/prompt/prompts.ts @@ -255,3 +255,67 @@ Formatting Requirements: - Use concise, actionable language. - Include relevant emojis in section headers for visual clarity (e.g., 📝, 🛡️, 🔍, 📚). `; + +export const starterPromptTitle1 = 'Alerts'; +export const starterPromptDescription1 = 'Most important alerts from the last 24 hrs'; +export const starterPromptIcon1 = 'bell'; +export const starterPromptPrompt1 = `🔍 Identify and Prioritize Today's Most Critical Alerts +Provide a structured summary of today's most significant alerts, including: +🛡️ Critical Alerts Overview +Highlight the most impactful alerts based on risk scores, severity, and affected entities. +Summarize key details such as alert name, risk score, severity, and associated users or hosts. +📊 Risk Context +Include user and host risk scores for each alert to provide additional context. +Reference relevant MITRE ATT&CK techniques, with hyperlinks to the official MITRE pages. +🚨 Why These Alerts Matter +Explain why these alerts are critical, focusing on potential business impact, lateral movement risks, or sensitive data exposure. +🔧 Recommended Next Steps +Provide actionable triage steps for each alert, such as: +Investigating the alert in Elastic Security. +Reviewing related events in Timelines. +Analyzing user and host behavior using Entity Analytics. +Suggest Elastic Defend endpoint response actions (e.g., isolate host, kill process, retrieve/delete file), with links to Elastic documentation. +📚 Documentation and References +Include direct links to Elastic Security documentation and relevant MITRE ATT&CK pages for further guidance. +Use markdown headers, tables, and code blocks for clarity. Include relevant emojis for visual distinction and ensure the response is concise, actionable, and tailored to Elastic Security workflows.`; +export const starterPromptDescription2 = 'Latest Elastic Security Labs research'; +export const starterPromptTitle2 = 'Research'; +export const starterPromptIcon2 = 'launch'; +export const starterPromptPrompt2 = `Retrieve and summarize the latest Elastic Security Labs articles one by one sorted by latest at the top. Ensure the response includes: +Article Summaries +Title and Link: Provide the title of each article with a hyperlink to the original content. +Publication Date: Include the date the article was published. +Key Insights: Summarize the main points or findings of each article in concise bullet points. +Relevant Threats or Techniques: Highlight any specific malware, attack techniques, or adversary behaviors discussed, with references to MITRE ATT&CK techniques (include hyperlinks to the official MITRE pages). +Practical Applications +Detection and Response Guidance: Provide actionable steps or recommendations based on the article's content, tailored for Elastic Security workflows. +Elastic Security Features: Highlight any Elastic Security features, detection rules, or tools mentioned in the articles, with links to relevant documentation. +Example Queries: If applicable, include example ES|QL or OSQuery Manager queries inspired by the article's findings, formatted as code blocks. +Documentation and Resources +Elastic Security Labs: Include a link to the Elastic Security Labs homepage. +Additional References: Provide links to any related Elastic documentation or external resources mentioned in the articles. +Formatting Requirements +Use markdown headers, tables, and code blocks for clarity. +Organize the response into visually distinct sections. +Use concise, actionable language.`; +export const starterPromptDescription3 = 'Generate ES|QL Queries'; +export const starterPromptTitle3 = 'Query'; +export const starterPromptIcon3 = 'esqlVis'; +export const starterPromptPrompt3 = + 'I need an Elastic ES|QL query to achieve the following goal:\n' + + 'Goal/Requirement:\n' + + '\n' + + 'Please:\n' + + 'Generate the ES|QL Query: Provide a complete ES|QL query tailored to the stated goal.\n' + + 'Explain the Query: Offer a brief explanation of each part of the query, including filters, fields, and logic used.\n' + + 'Optimize for Elastic Security: Suggest additional filters, aggregations, or enhancements to make the query more efficient and actionable within Elastic Security workflows.\n' + + 'Provide Documentation Links: Include links to relevant Elastic Security documentation for deeper understanding.\n' + + 'Formatting Requirements:\n' + + 'Use code blocks for the ES|QL query.\n' + + 'Include concise explanations in bullet points for clarity.\n' + + 'Highlight any advanced ES|QL features used in the query.\n'; +export const starterPromptDescription4 = 'Discover the types of questions you can ask'; +export const starterPromptTitle4 = 'Suggest'; +export const starterPromptIcon4 = 'sparkles'; +export const starterPromptPrompt4 = + 'Can you provide examples of questions I can ask about Elastic Security, such as investigating alerts, running ES|QL queries, incident response, or threat intelligence?'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index a543bd6dddd42..fb38c9666811c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -18,6 +18,7 @@ import { knowledgeBaseReference, } from '@kbn/elastic-assistant-common/impl/content_references/references'; import { Document } from 'langchain/document'; +import { getIsKnowledgeBaseInstalled } from '@kbn/elastic-assistant-plugin/server/routes/helpers'; import { APP_UI_ID } from '../../../../common'; const toolDetails = { @@ -51,9 +52,11 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { kbResource: SECURITY_LABS_RESOURCE, query: input.question, }); - if (docs.length === 0 && !params.isEnabledKnowledgeBase) { + // this value does not tell me if security labs exists + const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient); + if (docs.length === 0 && !isKnowledgeBaseInstalled) { // prompt to help user install knowledge base - return 'No relevant Elastic Security Labs content found for the provided query. Ensure knowledge base is installed.'; + return 'The "AI Assistant knowledge base" needs to be installed, containing the Security Labs content. Navigate to the Knowledge Base page in the AI Assistant Settings to install it.'; } const citedDocs = docs.map((doc) => { From 71dadcbfc606e07a42ea93100aaa59c6343c41ac Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 07:31:43 -0600 Subject: [PATCH 19/41] fix type --- .../impl/mock/test_providers/test_providers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index a37332b2cf68e..b8a68676a645b 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -38,6 +38,7 @@ export const mockAssistantAvailability: AssistantAvailability = { hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; /** A utility for wrapping children in the providers required to run tests */ From 4e4efa4e8365a9f588b1544de08c5ad8a663c319 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 07:45:53 -0600 Subject: [PATCH 20/41] rm outdated comment --- .../server/assistant/tools/security_labs/security_labs_tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index fb38c9666811c..47c3e5cebac64 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -52,7 +52,7 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { kbResource: SECURITY_LABS_RESOURCE, query: input.question, }); - // this value does not tell me if security labs exists + const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient); if (docs.length === 0 && !isKnowledgeBaseInstalled) { // prompt to help user install knowledge base From 9530c0b1f9de5a295962bafa6d4cf48da637d7bb Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 07:47:52 -0600 Subject: [PATCH 21/41] move check within docs.length 0 --- .../tools/security_labs/security_labs_tool.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index 47c3e5cebac64..c37d1e4e73690 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -53,10 +53,12 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { query: input.question, }); - const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient); - if (docs.length === 0 && !isKnowledgeBaseInstalled) { - // prompt to help user install knowledge base - return 'The "AI Assistant knowledge base" needs to be installed, containing the Security Labs content. Navigate to the Knowledge Base page in the AI Assistant Settings to install it.'; + if (docs.length === 0) { + const isKnowledgeBaseInstalled = await getIsKnowledgeBaseInstalled(kbDataClient); + if (!isKnowledgeBaseInstalled) { + // prompt to help user install knowledge base + return 'The "AI Assistant knowledge base" needs to be installed, containing the Security Labs content. Navigate to the Knowledge Base page in the AI Assistant Settings to install it.'; + } } const citedDocs = docs.map((doc) => { From 43884763d677f71943421a87a1aadd8fd0a6790b Mon Sep 17 00:00:00 2001 From: Karen Grigoryan Date: Tue, 24 Jun 2025 15:47:34 +0200 Subject: [PATCH 22/41] chore: add tests and fix edge case with chat title --- .../actions/chat/index.test.tsx | 220 +++++++++++++++++- .../data_quality_panel/actions/chat/index.tsx | 3 +- .../utils/get_formatted_check_time.test.ts | 8 + .../utils/get_formatted_check_time.ts | 4 +- 4 files changed, 225 insertions(+), 10 deletions(-) diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.test.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.test.tsx index 33e52a398e3c9..d7fc944ded5f2 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.test.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.test.tsx @@ -9,21 +9,225 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; import { ChatAction } from '.'; +import { TestDataQualityProviders } from '../../mock/test_providers/test_providers'; +import { NewChat, useFindPrompts } from '@kbn/elastic-assistant'; import { - TestDataQualityProviders, - TestExternalProviders, -} from '../../mock/test_providers/test_providers'; + DATA_QUALITY_PROMPT_CONTEXT_PILL, + DATA_QUALITY_SUGGESTED_USER_PROMPT, +} from '../../translations'; +import { getFormattedCheckTime } from '../../data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time'; + +jest.mock('@kbn/elastic-assistant', () => ({ + NewChat: jest.fn(({ children }) => ( + + )), + useFindPrompts: jest.fn().mockReturnValue({ + data: { prompts: [] }, + }), +})); + +const useFindPromptsMock = useFindPrompts as unknown as jest.Mock< + Pick, 'data'> +>; +const NewChatMock = NewChat as jest.MockedFunction; describe('ChatAction', () => { - it('should render new chat link', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render new chat link', async () => { render( - + + + + ); + + expect(screen.getByTestId('newChatLink')).toHaveTextContent('Ask Assistant'); + }); + + it('should pass correct props to NewChat with minimal required props', () => { + const markdownComment = 'test markdown comment'; + const indexName = 'test-index-name'; + + render( + + + + ); + + expect(NewChatMock).toHaveBeenCalledWith( + expect.objectContaining({ + asLink: true, + category: 'data-quality-dashboard', + conversationTitle: '--', + description: expect.any(String), + getPromptContext: expect.any(Function), + suggestedUserPrompt: expect.any(String), + tooltip: expect.any(String), + isAssistantEnabled: true, + iconType: null, + }), + {} + ); + }); + + it('should pass index name in NewChat description prop', () => { + const markdownComment = 'test markdown'; + const indexName = 'specific-test-index'; + + render( + + + + ); + + const callArgs = NewChatMock.mock.calls[0][0]; + + expect(callArgs.description).toBe(DATA_QUALITY_PROMPT_CONTEXT_PILL(indexName)); + }); + + describe('when checkedAt timestamp is provided', () => { + it('should pass correct conversation title with formatted timestamp', () => { + const markdownComment = 'test markdown'; + const indexName = 'test-index'; + const checkedAt = 1640995200000; + + render( + + + + ); + + const expectedTitle = `${indexName} - ${getFormattedCheckTime(checkedAt)}`; + + expect(NewChatMock).toHaveBeenCalledWith( + expect.objectContaining({ + conversationTitle: expectedTitle, + }), + {} + ); + }); + }); + + describe('when checkedAt timestamp is not provided', () => { + it('should replace conversation title with empty stat "--" fallback', () => { + const markdownComment = 'test markdown'; + const indexName = 'test-index'; + + render( - + - + ); + + expect(NewChatMock).toHaveBeenCalledWith( + expect.objectContaining({ + conversationTitle: '--', + }), + {} + ); + }); + }); + + describe('when dataQualityAnalysis prompt is available', () => { + it('should use it', () => { + const dqdPrompt = 'Custom data quality analysis prompt'; + const markdownComment = 'test markdown'; + const indexName = 'test-index'; + + useFindPromptsMock.mockReturnValue({ + data: { + prompts: [ + { promptId: 'dataQualityAnalysis', prompt: dqdPrompt }, + { promptId: 'other', prompt: 'Other prompt' }, + ], + }, + }); + + render( + + + + ); + + expect(NewChatMock).toHaveBeenCalledWith( + expect.objectContaining({ + suggestedUserPrompt: dqdPrompt, + }), + {} + ); + }); + }); + + describe('when dataQualityAnalysis prompt is not available', () => { + it('should use fallback prompt', () => { + const markdownComment = 'test markdown'; + const indexName = 'test-index'; + + (useFindPrompts as jest.Mock).mockReturnValue({ + data: { + prompts: [{ promptId: 'other', prompt: 'Other prompt' }], + }, + }); + + render( + + + + ); + + expect(NewChatMock).toHaveBeenCalledWith( + expect.objectContaining({ + suggestedUserPrompt: DATA_QUALITY_SUGGESTED_USER_PROMPT, + }), + {} + ); + }); + }); + + it('should call useFindPrompts hook with correct context and params', () => { + const markdownComment = 'test markdown'; + const indexName = 'test-index'; + + render( + + + ); - expect(screen.getByTestId('newChatLink')).toHaveTextContent('Ask Assistant'); + expect(useFindPromptsMock).toHaveBeenCalledWith({ + context: { + isAssistantEnabled: true, + httpFetch: expect.any(Function), + toasts: expect.any(Object), + }, + params: { + prompt_group_id: 'aiAssistant', + prompt_ids: ['dataQualityAnalysis'], + }, + }); + }); + + it('should provide NewChat getPromptContext function that returns markdownComment', async () => { + const markdownComment = 'test markdown comment for context'; + const indexName = 'test-index'; + + render( + + + + ); + + const callArgs = NewChatMock.mock.calls[0][0]; + const getPromptContext = callArgs.getPromptContext; + + await expect(getPromptContext()).resolves.toBe(markdownComment); }); }); diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx index fad7e80064bbb..a27e441a3240b 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/actions/chat/index.tsx @@ -41,7 +41,8 @@ interface Props { const ChatActionComponent: FC = ({ indexName, markdownComment, checkedAt }) => { const chatTitle = useMemo(() => { - return `${indexName} - ${getFormattedCheckTime(checkedAt)}`; + const checkedAtFormatted = getFormattedCheckTime(checkedAt); + return checkedAt && indexName ? `${indexName} - ${checkedAtFormatted}` : checkedAtFormatted; }, [checkedAt, indexName]); const styles = useStyles(); diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.test.ts b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.test.ts index 9956bbc525aa8..efb1d1fc5d236 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.test.ts +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.test.ts @@ -10,6 +10,7 @@ import moment from 'moment-timezone'; moment.tz.setDefault('UTC'); import { getFormattedCheckTime } from './get_formatted_check_time'; +import { EMPTY_STAT } from '../../../../../constants'; describe('getFormattedCheckTime', () => { it('returns formatted check time', () => { @@ -23,4 +24,11 @@ describe('getFormattedCheckTime', () => { expect(formattedCheckTime).toBe('--'); }); }); + + describe('when check time is not provided', () => { + it(`returns ${EMPTY_STAT} string`, () => { + const formattedCheckTime = getFormattedCheckTime(); + expect(formattedCheckTime).toBe(EMPTY_STAT); + }); + }); }); diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.ts b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.ts index acad51527bace..6acb68e949efa 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.ts +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/data_quality_details/indices_details/pattern/index_check_flyout/utils/get_formatted_check_time.ts @@ -10,4 +10,6 @@ import moment from 'moment'; import { EMPTY_STAT } from '../../../../../constants'; export const getFormattedCheckTime = (checkedAt?: number) => - moment(checkedAt).isValid() ? moment(checkedAt).format('MMM DD, YYYY @ HH:mm:ss') : EMPTY_STAT; + checkedAt && moment(checkedAt).isValid() + ? moment(checkedAt).format('MMM DD, YYYY @ HH:mm:ss') + : EMPTY_STAT; From 3b88071f8c4c4d56ad3e5618e91c059c32b52afc Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 07:52:16 -0600 Subject: [PATCH 23/41] fix type in DQD code --- .../data_quality_panel/mock/test_providers/test_providers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index 704a3f501a8f7..2e65adf9273a9 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -55,6 +55,7 @@ const TestExternalProvidersComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; const queryClient = new QueryClient({ defaultOptions: { From 7325e0c9dbae87ec5470813b0bd3376667664f20 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 08:02:28 -0600 Subject: [PATCH 24/41] fix types --- .../public/common/mock/mock_assistant_provider.tsx | 1 + .../flyout/document_details/right/hooks/use_assistant.test.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 29965d1bc61ff..8a07f874bc11e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -39,6 +39,7 @@ export const MockAssistantProviderComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; const chrome = chromeServiceMock.createStartContract(); chrome.getChromeStyle$.mockReturnValue(of('classic')); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx index 66f16c9280923..eac7230c2dad7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx @@ -36,6 +36,7 @@ describe('useAssistant', () => { hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }); useAssistantOverlayMock.mockReturnValue({ showAssistantOverlay: jest.fn, @@ -88,6 +89,7 @@ describe('useAssistant', () => { hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }); hookResult = renderUseAssistant(); From 8648778b7ac818c70092b879dd95f5c875e7ba2d Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 08:13:37 -0600 Subject: [PATCH 25/41] add connector_id to request --- .../impl/assistant/assistant_body/empty_convo.tsx | 4 +++- .../impl/assistant/assistant_body/index.tsx | 1 + .../impl/assistant/assistant_body/starter_prompts.tsx | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 89965f52a9333..ef2b75e86d64f 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -17,6 +17,7 @@ import { SetupKnowledgeBaseButton } from '../../knowledge_base/setup_knowledge_b import * as i18n from '../translations'; interface Props { + connectorId?: string; currentSystemPromptId: string | undefined; isSettingsModalVisible: boolean; setIsSettingsModalVisible: Dispatch>; @@ -30,6 +31,7 @@ const starterPromptWrapperClassName = css` export const EmptyConvo: React.FC = ({ allSystemPrompts, + connectorId, currentSystemPromptId, isSettingsModalVisible, setCurrentSystemPromptId, @@ -79,7 +81,7 @@ export const EmptyConvo: React.FC = ({ {assistantAvailability.isStarterPromptsEnabled && ( - + )} diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx index 2f2bcef847b5e..77d1cea588aeb 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/assistant_body/index.tsx @@ -130,6 +130,7 @@ export const AssistantBody: FunctionComponent = ({ setCurrentSystemPromptId={setCurrentSystemPromptId} setIsSettingsModalVisible={setIsSettingsModalVisible} setUserPrompt={setUserPrompt} + connectorId={currentConversation?.apiConfig?.connectorId} /> ) : ( >; } const starterPromptClassName = css` @@ -64,7 +65,7 @@ export const promptGroups = [ }, ]; -export const StarterPrompts: React.FC = ({ setUserPrompt }) => { +export const StarterPrompts: React.FC = ({ connectorId, setUserPrompt }) => { const { assistantAvailability: { isAssistantEnabled }, http, @@ -79,6 +80,7 @@ export const StarterPrompts: React.FC = ({ setUserPrompt }) => { toasts, }, params: { + connector_id: connectorId, prompt_group_id: 'aiAssistant', prompt_ids: getAllPromptIds(promptGroups), }, From 0a8685bbf638ffe1acd41b35b04afd1336a7eb84 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 08:22:49 -0600 Subject: [PATCH 26/41] add security_labs_tool tests --- .../security_labs/security_labs_tool.test.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts index 3c49c6563b89a..4836108b65085 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts @@ -15,7 +15,8 @@ import type { import { newContentReferencesStoreMock } from '@kbn/elastic-assistant-common/impl/content_references/content_references_store/__mocks__/content_references_store.mock'; import type { AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; import { Document } from 'langchain/document'; - +import { getIsKnowledgeBaseInstalled } from '@kbn/elastic-assistant-plugin/server/routes/helpers'; +jest.mock('@kbn/elastic-assistant-plugin/server/routes/helpers'); describe('SecurityLabsTool', () => { const contentReferencesStore = newContentReferencesStoreMock(); const getKnowledgeBaseDocumentEntries = jest.fn(); @@ -100,5 +101,23 @@ In previous publications,`, expect(result).toContain('Citation: {reference(exampleContentReferenceId)}'); }); + it('Responds with The "AI Assistant knowledge base" needs to be installed... when no docs and no kb install', async () => { + getKnowledgeBaseDocumentEntries.mockResolvedValue([]); + getIsKnowledgeBaseInstalled.mockResolvedValue(false); + const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool; + + const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' }); + + expect(result).toContain('The "AI Assistant knowledge base" needs to be installed'); + }); + it('Responds with empty response when no docs and kb is installed', async () => { + getKnowledgeBaseDocumentEntries.mockResolvedValue([]); + getIsKnowledgeBaseInstalled.mockResolvedValue(true); + const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool; + + const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' }); + + expect(result).toEqual('[]'); + }); }); }); From 0355d9943d8b622b3d0cbebf01c1c4070882a998 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 08:27:05 -0600 Subject: [PATCH 27/41] fix tests --- .../assistant/tools/security_labs/security_labs_tool.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts index 4836108b65085..040d755e26117 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.test.ts @@ -103,7 +103,7 @@ In previous publications,`, }); it('Responds with The "AI Assistant knowledge base" needs to be installed... when no docs and no kb install', async () => { getKnowledgeBaseDocumentEntries.mockResolvedValue([]); - getIsKnowledgeBaseInstalled.mockResolvedValue(false); + (getIsKnowledgeBaseInstalled as jest.Mock).mockResolvedValue(false); const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool; const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' }); @@ -112,7 +112,7 @@ In previous publications,`, }); it('Responds with empty response when no docs and kb is installed', async () => { getKnowledgeBaseDocumentEntries.mockResolvedValue([]); - getIsKnowledgeBaseInstalled.mockResolvedValue(true); + (getIsKnowledgeBaseInstalled as jest.Mock).mockResolvedValue(true); const tool = SECURITY_LABS_KNOWLEDGE_BASE_TOOL.getTool(defaultArgs) as DynamicStructuredTool; const result = await tool.func({ query: 'What is Kibana Security?', product: 'kibana' }); From de70098ccd697a0351c21627a18604663a98ef4e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 24 Jun 2025 19:02:04 +0000 Subject: [PATCH 28/41] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../plugins/security_solution/public/assistant/provider.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index 7158a51f48b36..fd1aabee01ae3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -75,7 +75,7 @@ export const AssistantProvider: FC> = ({ children }) await createBasePrompts(notifications, http); } // eslint-disable-next-line no-empty - } catch (e) { } + } catch (e) {} } }); createSecurityPrompts(); @@ -100,13 +100,13 @@ export const AssistantProvider: FC> = ({ children }) }); useEffect(() => { - const unmountPromptContexts = elasticAssistantSharedState.promptContexts.setPromptContext(PROMPT_CONTEXTS); + const unmountPromptContexts = + elasticAssistantSharedState.promptContexts.setPromptContext(PROMPT_CONTEXTS); return () => { unmountPromptContexts(); }; }, [PROMPT_CONTEXTS]); - if (!assistantContextValue) { return null; } From 5f6c67a514efce216c1db4f46831b5ca746a5bd6 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 14:29:44 -0600 Subject: [PATCH 29/41] fix linter --- .../plugins/security_solution/public/assistant/provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index fd1aabee01ae3..c237a274a3268 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -105,7 +105,7 @@ export const AssistantProvider: FC> = ({ children }) return () => { unmountPromptContexts(); }; - }, [PROMPT_CONTEXTS]); + }, [elasticAssistantSharedState.promptContexts, PROMPT_CONTEXTS]); if (!assistantContextValue) { return null; From dfac35b1118b00fb0386086df22897b0f9501c71 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 15:04:27 -0600 Subject: [PATCH 30/41] hopefully fix failing API call --- .../plugins/security_solution/public/assistant/provider.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index c237a274a3268..80c47c2ceaa55 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -89,7 +89,10 @@ export const AssistantProvider: FC> = ({ children }) const PROMPT_CONTEXTS = useFindPromptContexts({ context: { - isAssistantEnabled: assistantAvailability.isAssistantEnabled, + isAssistantEnabled: + hasEnterpriseLicence && + assistantAvailability.isAssistantEnabled && + assistantAvailability.hasAssistantPrivilege, httpFetch: http.fetch, toasts: notifications.toasts, }, From b3cc30892915359c38f1ca20a649733d0d957903 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 18:45:14 -0600 Subject: [PATCH 31/41] fix?! --- .../public/assistant/provider.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx index 80c47c2ceaa55..3055752b3850b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/provider.tsx @@ -13,7 +13,7 @@ import { bulkUpdatePrompts, } from '@kbn/elastic-assistant'; -import { once } from 'lodash/fp'; +import { once, isEmpty } from 'lodash/fp'; import type { HttpSetup } from '@kbn/core-http-browser'; import useObservable from 'react-use/lib/useObservable'; import { useKibana } from '../common/lib/kibana'; @@ -101,14 +101,15 @@ export const AssistantProvider: FC> = ({ children }) prompt_ids: ['alertEvaluation', 'dataQualityAnalysis', 'ruleAnalysis'], }, }); - + const promptContext = useObservable( + elasticAssistantSharedState.promptContexts.getPromptContext$(), + {} + ); useEffect(() => { - const unmountPromptContexts = + if (isEmpty(promptContext)) { elasticAssistantSharedState.promptContexts.setPromptContext(PROMPT_CONTEXTS); - return () => { - unmountPromptContexts(); - }; - }, [elasticAssistantSharedState.promptContexts, PROMPT_CONTEXTS]); + } + }, [elasticAssistantSharedState.promptContexts, promptContext, PROMPT_CONTEXTS]); if (!assistantContextValue) { return null; From e21d2a0bb443d20ad617973b2c3ddf8a48fd9c98 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 19:00:15 -0600 Subject: [PATCH 32/41] refetchOnWindowFocus: false --- .../impl/assistant/api/security_ai_prompts/use_find_prompts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts index 00a93c99a2511..01c9cbf773592 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/api/security_ai_prompts/use_find_prompts.ts @@ -69,6 +69,7 @@ export const useFindPrompts = (payload: UseFindPromptsParams) => { prompts: [], }, keepPreviousData: true, + refetchOnWindowFocus: false, enabled: isAssistantEnabled, } ); From 053caa765a95fc691fbda50a20c4c49dd493a66b Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 21:10:21 -0600 Subject: [PATCH 33/41] change feature flag --- .../impl/assistant_context/types.tsx | 2 -- .../packages/shared/kbn-elastic-assistant/index.ts | 2 ++ .../assistant_availability/use_assistant_availability.ts | 9 ++++++++- .../security_solution/common/experimental_features.ts | 4 ---- .../assistant/use_assistant_availability/index.tsx | 6 ------ 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index ce024d7d02178..3c8ecbb2a70dc 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -74,8 +74,6 @@ export interface AssistantAvailability { hasUpdateAIAssistantAnonymization: boolean; // When true, user has `Edit` privilege for `Global Knowledge Base` hasManageGlobalKnowledgeBase: boolean; - // remove once product has signed off on prompt text - isStarterPromptsEnabled: boolean; } export type GetAssistantMessages = (commentArgs: { diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts index 569ed723f2995..540c506a98cdf 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/index.ts @@ -201,4 +201,6 @@ export interface UseAssistantAvailability { hasUpdateAIAssistantAnonymization: boolean; // When true, user has `Edit` privilege for `Global Knowledge Base` hasManageGlobalKnowledgeBase: boolean; + // remove once product has signed off on prompt text + isStarterPromptsEnabled: boolean; } diff --git a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts index 54a99d76f5902..9c78ca410ef8c 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.ts @@ -11,9 +11,13 @@ import { useKibana } from '../../context/typed_kibana_context/typed_kibana_conte import { useLicense } from '../licence/use_licence'; +export const STARTER_PROMPTS_FEATURE_FLAG = 'elasticAssistant.starterPromptsEnabled' as const; export const useAssistantAvailability = (): UseAssistantAvailability => { const isEnterprise = useLicense().isEnterprise(); - const capabilities = useKibana().services.application.capabilities; + const { + application: { capabilities }, + featureFlags, + } = useKibana().services; const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true; const hasUpdateAIAssistantAnonymization = @@ -32,11 +36,14 @@ export const useAssistantAvailability = (): UseAssistantAvailability => { capabilities.actions?.delete === true && capabilities.actions?.save === true; + const isStarterPromptsEnabled = featureFlags.getBooleanValue(STARTER_PROMPTS_FEATURE_FLAG, false); + return { hasSearchAILakeConfigurations, hasAssistantPrivilege, hasConnectorsAllPrivilege, hasConnectorsReadPrivilege, + isStarterPromptsEnabled, isAssistantEnabled: isEnterprise, hasUpdateAIAssistantAnonymization, hasManageGlobalKnowledgeBase, diff --git a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts index 51f3085fa30aa..df7183cf90c33 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts @@ -279,10 +279,6 @@ export const allowedExperimentalValues = Object.freeze({ * Enables advanced mode for Trusted Apps creation and update */ trustedAppsAdvancedMode: false, - /** - * Enables the starter prompts in the Elastic Assistant - */ - starterPromptsEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx index a88850db20948..7d7ca3fabf96e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx @@ -6,7 +6,6 @@ */ import type { AssistantAvailability } from '@kbn/elastic-assistant'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { useLicense } from '../../common/hooks/use_license'; import { useKibana } from '../../common/lib/kibana'; import { ASSISTANT_FEATURE_ID, SECURITY_FEATURE_ID } from '../../../common/constants'; @@ -31,17 +30,12 @@ export const useAssistantAvailability = (): AssistantAvailability => { capabilities.actions?.delete === true && capabilities.actions?.save === true; - const starterPromptsEnabled = useIsExperimentalFeatureEnabled('starterPromptsEnabled'); - // remove once product has signed off on prompt text - const isStarterPromptsEnabled = starterPromptsEnabled; - return { hasSearchAILakeConfigurations, hasAssistantPrivilege, hasConnectorsAllPrivilege, hasConnectorsReadPrivilege, isAssistantEnabled: isEnterprise, - isStarterPromptsEnabled, hasUpdateAIAssistantAnonymization, hasManageGlobalKnowledgeBase, }; From feee4001368c67105fc04bfb5b7cad65158245b7 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 21:13:25 -0600 Subject: [PATCH 34/41] rm ff value from security_solution --- .../data_quality_panel/mock/test_providers/test_providers.tsx | 1 - .../public/common/mock/mock_assistant_provider.tsx | 1 - .../flyout/document_details/right/hooks/use_assistant.test.tsx | 2 -- 3 files changed, 4 deletions(-) diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index e9d5c049f68ad..b4a635ebc4bcf 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -59,7 +59,6 @@ const TestExternalProvidersComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, - isStarterPromptsEnabled: true, }; const queryClient = new QueryClient({ defaultOptions: { diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 0b19233080c46..3b72222a2475f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -40,7 +40,6 @@ export const MockAssistantProviderComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, - isStarterPromptsEnabled: true, }; const chrome = chromeServiceMock.createStartContract(); chrome.getChromeStyle$.mockReturnValue(of('classic')); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx index eac7230c2dad7..66f16c9280923 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx @@ -36,7 +36,6 @@ describe('useAssistant', () => { hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, - isStarterPromptsEnabled: true, }); useAssistantOverlayMock.mockReturnValue({ showAssistantOverlay: jest.fn, @@ -89,7 +88,6 @@ describe('useAssistant', () => { hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, - isStarterPromptsEnabled: true, }); hookResult = renderUseAssistant(); From 1dc8e71f669b7754f568f24ba3713c3294510a97 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 21:27:58 -0600 Subject: [PATCH 35/41] feature flag to kbn-elastic-assistant --- .../kbn-elastic-assistant/impl/assistant_context/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx index 3c8ecbb2a70dc..ff4e306844ee3 100644 --- a/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx +++ b/x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant_context/types.tsx @@ -74,6 +74,7 @@ export interface AssistantAvailability { hasUpdateAIAssistantAnonymization: boolean; // When true, user has `Edit` privilege for `Global Knowledge Base` hasManageGlobalKnowledgeBase: boolean; + isStarterPromptsEnabled: boolean; } export type GetAssistantMessages = (commentArgs: { From e17808ca7643e6cf2bbcdb521192eb8598c7df97 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 21:35:54 -0600 Subject: [PATCH 36/41] type fix --- .../data_quality_panel/mock/test_providers/test_providers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx index b4a635ebc4bcf..e9d5c049f68ad 100644 --- a/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx +++ b/x-pack/solutions/security/packages/ecs-data-quality-dashboard/impl/data_quality_panel/mock/test_providers/test_providers.tsx @@ -59,6 +59,7 @@ const TestExternalProvidersComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; const queryClient = new QueryClient({ defaultOptions: { From 122ab71cacd031d2b11b412656c7ebf2f12c7c7c Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 21:51:21 -0600 Subject: [PATCH 37/41] revert type move --- .../use_assistant_availability/index.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx index 7d7ca3fabf96e..8e3ee14083b23 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx @@ -5,11 +5,27 @@ * 2.0. */ -import type { AssistantAvailability } from '@kbn/elastic-assistant'; import { useLicense } from '../../common/hooks/use_license'; import { useKibana } from '../../common/lib/kibana'; import { ASSISTANT_FEATURE_ID, SECURITY_FEATURE_ID } from '../../../common/constants'; +export interface AssistantAvailability { + // True when searchAiLake configurations is available + hasSearchAILakeConfigurations: boolean; + // True when user is Enterprise, or Security Complete PLI for serverless. When false, the Assistant is disabled and unavailable + isAssistantEnabled: boolean; + // When true, the Assistant is hidden and unavailable + hasAssistantPrivilege: boolean; + // When true, user has `All` privilege for `Connectors and Actions` (show/execute/delete/save ui capabilities) + hasConnectorsAllPrivilege: boolean; + // When true, user has `Read` privilege for `Connectors and Actions` (show/execute ui capabilities) + hasConnectorsReadPrivilege: boolean; + // When true, user has `Edit` privilege for `AnonymizationFields` + hasUpdateAIAssistantAnonymization: boolean; + // When true, user has `Edit` privilege for `Global Knowledge Base` + hasManageGlobalKnowledgeBase: boolean; +} + export const useAssistantAvailability = (): AssistantAvailability => { const isEnterprise = useLicense().isEnterprise(); const capabilities = useKibana().services.application.capabilities; From ea0bd6fcfbe535bb8d8026e122f78c120968f327 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 21:52:10 -0600 Subject: [PATCH 38/41] really though --- .../public/assistant/use_assistant_availability/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx index 8e3ee14083b23..e08aa8579c2ae 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/use_assistant_availability/index.tsx @@ -9,10 +9,10 @@ import { useLicense } from '../../common/hooks/use_license'; import { useKibana } from '../../common/lib/kibana'; import { ASSISTANT_FEATURE_ID, SECURITY_FEATURE_ID } from '../../../common/constants'; -export interface AssistantAvailability { +export interface UseAssistantAvailability { // True when searchAiLake configurations is available hasSearchAILakeConfigurations: boolean; - // True when user is Enterprise, or Security Complete PLI for serverless. When false, the Assistant is disabled and unavailable + // True when user is Enterprise. When false, the Assistant is disabled and unavailable isAssistantEnabled: boolean; // When true, the Assistant is hidden and unavailable hasAssistantPrivilege: boolean; @@ -26,7 +26,7 @@ export interface AssistantAvailability { hasManageGlobalKnowledgeBase: boolean; } -export const useAssistantAvailability = (): AssistantAvailability => { +export const useAssistantAvailability = (): UseAssistantAvailability => { const isEnterprise = useLicense().isEnterprise(); const capabilities = useKibana().services.application.capabilities; const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true; From 25b0c00958e77871472c9abdcdb38c4ba3a43937 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 22:02:10 -0600 Subject: [PATCH 39/41] fix type reference from mock --- .../public/common/mock/mock_assistant_provider.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index 3b72222a2475f..efbeed862d8de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -8,16 +8,16 @@ import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock'; import React from 'react'; -import type { AssistantAvailability } from '@kbn/elastic-assistant'; import { AssistantProvider } from '@kbn/elastic-assistant'; import type { UserProfileService } from '@kbn/core/public'; import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { of } from 'rxjs'; import { useAssistantContextValue } from '@kbn/elastic-assistant/impl/assistant_context'; import { docLinksServiceMock } from '@kbn/core/public/mocks'; +import type { UseAssistantAvailability } from '../../assistant/use_assistant_availability'; interface Props { - assistantAvailability?: AssistantAvailability; + assistantAvailability?: UseAssistantAvailability; children: React.ReactNode; } @@ -32,7 +32,7 @@ export const MockAssistantProviderComponent: React.FC = ({ const actionTypeRegistry = actionTypeRegistryMock.create(); const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); const mockNavigateToApp = jest.fn(); - const defaultAssistantAvailability: AssistantAvailability = { + const defaultAssistantAvailability: UseAssistantAvailability = { hasSearchAILakeConfigurations: false, hasAssistantPrivilege: false, hasConnectorsAllPrivilege: true, From 770aaadfcaf86abacc94d4e1ff0d456b48c8c058 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 22:04:10 -0600 Subject: [PATCH 40/41] fix mock type --- .../public/common/mock/mock_assistant_provider.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx index efbeed862d8de..0b19233080c46 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/mock/mock_assistant_provider.tsx @@ -8,16 +8,16 @@ import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock'; import React from 'react'; +import type { AssistantAvailability } from '@kbn/elastic-assistant'; import { AssistantProvider } from '@kbn/elastic-assistant'; import type { UserProfileService } from '@kbn/core/public'; import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { of } from 'rxjs'; import { useAssistantContextValue } from '@kbn/elastic-assistant/impl/assistant_context'; import { docLinksServiceMock } from '@kbn/core/public/mocks'; -import type { UseAssistantAvailability } from '../../assistant/use_assistant_availability'; interface Props { - assistantAvailability?: UseAssistantAvailability; + assistantAvailability?: AssistantAvailability; children: React.ReactNode; } @@ -32,7 +32,7 @@ export const MockAssistantProviderComponent: React.FC = ({ const actionTypeRegistry = actionTypeRegistryMock.create(); const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' }); const mockNavigateToApp = jest.fn(); - const defaultAssistantAvailability: UseAssistantAvailability = { + const defaultAssistantAvailability: AssistantAvailability = { hasSearchAILakeConfigurations: false, hasAssistantPrivilege: false, hasConnectorsAllPrivilege: true, @@ -40,6 +40,7 @@ export const MockAssistantProviderComponent: React.FC = ({ hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, }; const chrome = chromeServiceMock.createStartContract(); chrome.getChromeStyle$.mockReturnValue(of('classic')); From 15e203c1d821ff52f5d8bf326f52239c40d65012 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Tue, 24 Jun 2025 23:50:16 -0600 Subject: [PATCH 41/41] fix jest tests --- .../assistant_provider.test.tsx | 4 ++++ .../use_assistant_availability.test.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx b/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx index be2f7854093da..2bed09bd68558 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/context/assistant_context/assistant_provider.test.tsx @@ -40,6 +40,9 @@ describe('AssistantProvider', () => { http: mockHttp, notifications, elasticAssistantSharedState, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(false), + }, }} > {children} @@ -63,6 +66,7 @@ describe('AssistantProvider', () => { hasSearchAILakeConfigurations: expect.any(Boolean), hasUpdateAIAssistantAnonymization: expect.any(Boolean), isAssistantEnabled: expect.any(Boolean), + isStarterPromptsEnabled: expect.any(Boolean), }), assistantFeatures: expect.objectContaining({ advancedEsqlGeneration: expect.any(Boolean), diff --git a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts index 07e66144da26b..2b7c8765d9479 100644 --- a/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts +++ b/x-pack/solutions/security/plugins/elastic_assistant/public/src/hooks/assistant_availability/use_assistant_availability.test.ts @@ -47,6 +47,9 @@ describe('useAssistantAvailability', () => { }, }, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(true), + }, }, } as unknown as ReturnType); @@ -58,6 +61,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, hasUpdateAIAssistantAnonymization: true, hasManageGlobalKnowledgeBase: true, }); @@ -88,6 +92,9 @@ describe('useAssistantAvailability', () => { }, }, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(false), + }, }, } as unknown as ReturnType); @@ -99,6 +106,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: false, isAssistantEnabled: false, + isStarterPromptsEnabled: false, hasUpdateAIAssistantAnonymization: false, hasManageGlobalKnowledgeBase: false, }); @@ -129,6 +137,9 @@ describe('useAssistantAvailability', () => { }, }, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(true), + }, }, } as unknown as ReturnType); @@ -140,6 +151,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: true, isAssistantEnabled: true, + isStarterPromptsEnabled: true, hasUpdateAIAssistantAnonymization: false, hasManageGlobalKnowledgeBase: false, }); @@ -155,6 +167,9 @@ describe('useAssistantAvailability', () => { application: { capabilities: {}, }, + featureFlags: { + getBooleanValue: jest.fn().mockReturnValue(true), + }, }, } as unknown as ReturnType); @@ -166,6 +181,7 @@ describe('useAssistantAvailability', () => { hasConnectorsAllPrivilege: false, hasConnectorsReadPrivilege: false, isAssistantEnabled: true, + isStarterPromptsEnabled: true, hasUpdateAIAssistantAnonymization: false, hasManageGlobalKnowledgeBase: false, });