mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Observability AI Assistant Service + Client
This commit is contained in:
parent
26bf6172a0
commit
d98b43ac19
22 changed files with 1205 additions and 17 deletions
|
@ -772,6 +772,7 @@
|
|||
"@opentelemetry/sdk-metrics-base": "^0.31.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.4.0",
|
||||
"@reduxjs/toolkit": "1.7.2",
|
||||
"@sindresorhus/fnv1a": "^3.0.0",
|
||||
"@slack/webhook": "^5.0.4",
|
||||
"@tanstack/react-query": "^4.29.12",
|
||||
"@tanstack/react-query-devtools": "^4.29.12",
|
||||
|
@ -905,7 +906,7 @@
|
|||
"normalize-path": "^3.0.0",
|
||||
"object-hash": "^1.3.1",
|
||||
"object-path-immutable": "^3.1.1",
|
||||
"openai": "^3.2.1",
|
||||
"openai": "^3.3.0",
|
||||
"openpgp": "5.3.0",
|
||||
"opn": "^5.5.0",
|
||||
"ora": "^4.0.4",
|
||||
|
@ -1550,4 +1551,4 @@
|
|||
"xmlbuilder": "13.0.2",
|
||||
"yargs": "^15.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ export { formatRequest } from './src/format_request';
|
|||
export { parseEndpoint } from './src/parse_endpoint';
|
||||
export { decodeRequestParams } from './src/decode_request_params';
|
||||
export { routeValidationObject } from './src/route_validation_object';
|
||||
export { registerRoutes } from './src/register_routes';
|
||||
|
||||
export type {
|
||||
RouteRepositoryClient,
|
||||
ReturnOf,
|
||||
|
|
141
packages/kbn-server-route-repository/src/register_routes.ts
Normal file
141
packages/kbn-server-route-repository/src/register_routes.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import { isBoom } from '@hapi/boom';
|
||||
import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import type { KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server';
|
||||
import type { CoreSetup } from '@kbn/core-lifecycle-server';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import * as t from 'io-ts';
|
||||
import { merge, pick } from 'lodash';
|
||||
import { decodeRequestParams } from './decode_request_params';
|
||||
import { parseEndpoint } from './parse_endpoint';
|
||||
import { routeValidationObject } from './route_validation_object';
|
||||
import type { ServerRoute, ServerRouteCreateOptions } from './typings';
|
||||
|
||||
const CLIENT_CLOSED_REQUEST = {
|
||||
statusCode: 499,
|
||||
body: {
|
||||
message: 'Client closed request',
|
||||
},
|
||||
};
|
||||
|
||||
export function registerRoutes({
|
||||
core,
|
||||
repository,
|
||||
logger,
|
||||
dependencies,
|
||||
}: {
|
||||
core: CoreSetup;
|
||||
repository: Record<string, ServerRoute<string, any, any, any, ServerRouteCreateOptions>>;
|
||||
logger: Logger;
|
||||
dependencies: Record<string, any>;
|
||||
}) {
|
||||
const routes = Object.values(repository);
|
||||
|
||||
const router = core.http.createRouter();
|
||||
|
||||
routes.forEach((route) => {
|
||||
const { params, endpoint, options, handler } = route;
|
||||
|
||||
const { method, pathname, version } = parseEndpoint(endpoint);
|
||||
|
||||
const wrappedHandler = async (
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest,
|
||||
response: KibanaResponseFactory
|
||||
) => {
|
||||
try {
|
||||
const runtimeType = params || t.strict({});
|
||||
|
||||
const validatedParams = decodeRequestParams(
|
||||
pick(request, 'params', 'body', 'query'),
|
||||
runtimeType
|
||||
);
|
||||
|
||||
const { aborted, data } = await Promise.race([
|
||||
handler({
|
||||
request,
|
||||
context,
|
||||
params: validatedParams,
|
||||
...dependencies,
|
||||
}).then((value) => {
|
||||
return {
|
||||
aborted: false,
|
||||
data: value,
|
||||
};
|
||||
}),
|
||||
request.events.aborted$.toPromise().then(() => {
|
||||
return {
|
||||
aborted: true,
|
||||
data: undefined,
|
||||
};
|
||||
}),
|
||||
]);
|
||||
|
||||
if (aborted) {
|
||||
return response.custom(CLIENT_CLOSED_REQUEST);
|
||||
}
|
||||
|
||||
const body = data || {};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
|
||||
const opts = {
|
||||
statusCode: 500,
|
||||
body: {
|
||||
message: error.message,
|
||||
attributes: {
|
||||
data: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (error instanceof errors.RequestAbortedError) {
|
||||
return response.custom(merge(opts, CLIENT_CLOSED_REQUEST));
|
||||
}
|
||||
|
||||
if (isBoom(error)) {
|
||||
opts.statusCode = error.output.statusCode;
|
||||
opts.body.attributes.data = error?.data;
|
||||
}
|
||||
|
||||
return response.custom(opts);
|
||||
}
|
||||
};
|
||||
|
||||
logger.debug(`Registering endpoint ${endpoint}`);
|
||||
|
||||
if (!version) {
|
||||
router[method](
|
||||
{
|
||||
path: pathname,
|
||||
options,
|
||||
validate: routeValidationObject,
|
||||
},
|
||||
wrappedHandler
|
||||
);
|
||||
} else {
|
||||
router.versioned[method]({
|
||||
path: pathname,
|
||||
access: pathname.startsWith('/internal/') ? 'internal' : 'public',
|
||||
options,
|
||||
}).addVersion(
|
||||
{
|
||||
version,
|
||||
validate: {
|
||||
request: routeValidationObject,
|
||||
},
|
||||
},
|
||||
wrappedHandler
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -13,7 +13,11 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/config-schema",
|
||||
"@kbn/io-ts-utils"
|
||||
"@kbn/io-ts-utils",
|
||||
"@kbn/core-http-request-handler-context-server",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/core-lifecycle-server",
|
||||
"@kbn/logging"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
56
x-pack/plugins/observability_ai_assistant/common/types.ts
Normal file
56
x-pack/plugins/observability_ai_assistant/common/types.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { Serializable } from '@kbn/utility-types';
|
||||
|
||||
export enum MessageRole {
|
||||
System = 'system',
|
||||
Assistant = 'assistant',
|
||||
User = 'user',
|
||||
Function = 'function',
|
||||
Event = 'event',
|
||||
Elastic = 'elastic',
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
'@timestamp': string;
|
||||
message: {
|
||||
content?: string;
|
||||
name?: string;
|
||||
role: MessageRole;
|
||||
function_call?: {
|
||||
name: string;
|
||||
args?: Serializable;
|
||||
trigger: MessageRole.Assistant | MessageRole.User | MessageRole.Elastic;
|
||||
};
|
||||
data?: Serializable;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
'@timestamp': string;
|
||||
user: {
|
||||
id?: string;
|
||||
name: string;
|
||||
};
|
||||
conversation: {
|
||||
id: string;
|
||||
title: string;
|
||||
last_updated: string;
|
||||
};
|
||||
messages: Message[];
|
||||
labels: Record<string, string>;
|
||||
numeric_labels: Record<string, number>;
|
||||
namespace: string;
|
||||
}
|
||||
|
||||
export type ConversationRequestBase = Omit<Conversation, 'user' | 'conversation' | 'namespace'> & {
|
||||
conversation: { title: string };
|
||||
};
|
||||
|
||||
export type ConversationCreateRequest = ConversationRequestBase;
|
||||
export type ConversationUpdateRequest = ConversationRequestBase & { conversation: { id: string } };
|
|
@ -3,14 +3,17 @@
|
|||
"id": "@kbn/observability-ai-assistant-plugin",
|
||||
"owner": "@elastic/apm-ui",
|
||||
"plugin": {
|
||||
"id": "observabilityAiAssistant",
|
||||
"id": "observabilityAIAssistant",
|
||||
"server": true,
|
||||
"browser": true,
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"observabilityAiAssistant"
|
||||
"observabilityAIAssistant"
|
||||
],
|
||||
"requiredPlugins": [
|
||||
"triggersActionsUi",
|
||||
"actions"
|
||||
],
|
||||
"requiredPlugins": [],
|
||||
"optionalPlugins": [],
|
||||
"extraPublicDirs": []
|
||||
}
|
||||
|
|
|
@ -4,12 +4,20 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type {
|
||||
TriggersAndActionsUIPublicPluginSetup,
|
||||
TriggersAndActionsUIPublicPluginStart,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface*/
|
||||
|
||||
export interface ObservabilityAIAssistantPluginStart {}
|
||||
export interface ObservabilityAIAssistantPluginSetup {}
|
||||
export interface ObservabilityAIAssistantPluginSetupDependencies {}
|
||||
export interface ObservabilityAIAssistantPluginStartDependencies {}
|
||||
export interface ObservabilityAIAssistantPluginSetupDependencies {
|
||||
triggersActions: TriggersAndActionsUIPublicPluginSetup;
|
||||
}
|
||||
export interface ObservabilityAIAssistantPluginStartDependencies {
|
||||
triggersActions: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export interface ConfigSchema {}
|
||||
|
|
|
@ -12,7 +12,11 @@ import type {
|
|||
Plugin,
|
||||
PluginInitializerContext,
|
||||
} from '@kbn/core/server';
|
||||
import { mapValues } from 'lodash';
|
||||
import type { ObservabilityAIAssistantConfig } from './config';
|
||||
import { registerServerRoutes } from './routes/register_routes';
|
||||
import { ObservabilityAIAssistantRouteHandlerResources } from './routes/types';
|
||||
import { ObservabilityAIAssistantService } from './service';
|
||||
import {
|
||||
ObservabilityAIAssistantPluginSetup,
|
||||
ObservabilityAIAssistantPluginStart,
|
||||
|
@ -35,17 +39,44 @@ export class ObservabilityAIAssistantPlugin
|
|||
}
|
||||
public start(
|
||||
core: CoreStart,
|
||||
plugins: ObservabilityAIAssistantPluginSetupDependencies
|
||||
plugins: ObservabilityAIAssistantPluginStartDependencies
|
||||
): ObservabilityAIAssistantPluginStart {
|
||||
return {};
|
||||
}
|
||||
public setup(
|
||||
core: CoreSetup<
|
||||
ObservabilityAIAssistantPluginSetupDependencies,
|
||||
ObservabilityAIAssistantPluginStartDependencies,
|
||||
ObservabilityAIAssistantPluginStart
|
||||
>,
|
||||
plugins: ObservabilityAIAssistantPluginSetupDependencies
|
||||
): ObservabilityAIAssistantPluginSetup {
|
||||
const routeHandlerPlugins = mapValues(plugins, (value, key) => {
|
||||
return {
|
||||
setup: value,
|
||||
start: () =>
|
||||
core.getStartServices().then((services) => {
|
||||
const [, pluginsStartContracts] = services;
|
||||
return pluginsStartContracts[
|
||||
key as keyof ObservabilityAIAssistantPluginStartDependencies
|
||||
];
|
||||
}),
|
||||
};
|
||||
}) as ObservabilityAIAssistantRouteHandlerResources['plugins'];
|
||||
|
||||
const service = new ObservabilityAIAssistantService({
|
||||
logger: this.logger.get('service'),
|
||||
core,
|
||||
});
|
||||
|
||||
registerServerRoutes({
|
||||
core,
|
||||
logger: this.logger,
|
||||
dependencies: {
|
||||
plugins: routeHandlerPlugins,
|
||||
service,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import type { IncomingMessage } from 'http';
|
||||
import { notImplemented } from '@hapi/boom';
|
||||
import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';
|
||||
import { conversationRt } from '../runtime_types';
|
||||
|
||||
const chatRoute = createObservabilityAIAssistantServerRoute({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/chat',
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
},
|
||||
params: t.type({
|
||||
body: t.type({
|
||||
conversation: conversationRt,
|
||||
connectorId: t.string,
|
||||
}),
|
||||
}),
|
||||
handler: async (resources): Promise<IncomingMessage> => {
|
||||
const { request, params, service } = resources;
|
||||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
if (!client) {
|
||||
throw notImplemented();
|
||||
}
|
||||
|
||||
return client.chat({
|
||||
messages: params.body.conversation.messages,
|
||||
connectorId: params.body.connectorId,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const chatRoutes = {
|
||||
...chatRoute,
|
||||
};
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 { notImplemented } from '@hapi/boom';
|
||||
import * as t from 'io-ts';
|
||||
import { merge } from 'lodash';
|
||||
import { Conversation } from '../../../common/types';
|
||||
import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route';
|
||||
import { conversationCreateRt, conversationUpdateRt } from '../runtime_types';
|
||||
|
||||
const getConversationRoute = createObservabilityAIAssistantServerRoute({
|
||||
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
conversationId: t.string,
|
||||
}),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
},
|
||||
handler: async (resources): Promise<void> => {
|
||||
const { service, request, params } = resources;
|
||||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
if (!client) {
|
||||
throw notImplemented();
|
||||
}
|
||||
|
||||
return client.get(params.path.conversationId);
|
||||
},
|
||||
});
|
||||
|
||||
const findConversationsRoute = createObservabilityAIAssistantServerRoute({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/conversations',
|
||||
params: t.partial({
|
||||
body: t.partial({
|
||||
query: t.string,
|
||||
}),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
},
|
||||
handler: async (resources): Promise<{ conversations: Conversation[] }> => {
|
||||
const { service, request, params } = resources;
|
||||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
if (!client) {
|
||||
throw notImplemented();
|
||||
}
|
||||
|
||||
return client.find({ query: params?.body?.query });
|
||||
},
|
||||
});
|
||||
|
||||
const createConversationRoute = createObservabilityAIAssistantServerRoute({
|
||||
endpoint: 'PUT /internal/observability_ai_assistant/conversation',
|
||||
params: t.type({
|
||||
body: t.type({
|
||||
conversation: conversationCreateRt,
|
||||
}),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
},
|
||||
handler: async (resources): Promise<Conversation> => {
|
||||
const { service, request, params } = resources;
|
||||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
if (!client) {
|
||||
throw notImplemented();
|
||||
}
|
||||
|
||||
return client.create(params.body.conversation);
|
||||
},
|
||||
});
|
||||
|
||||
const updateConversationRoute = createObservabilityAIAssistantServerRoute({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/conversation/{conversationId}',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
conversationId: t.string,
|
||||
}),
|
||||
body: t.type({
|
||||
conversation: conversationUpdateRt,
|
||||
}),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
},
|
||||
handler: async (resources): Promise<Conversation> => {
|
||||
const { service, request, params } = resources;
|
||||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
if (!client) {
|
||||
throw notImplemented();
|
||||
}
|
||||
|
||||
return client.create(
|
||||
merge({}, params.body.conversation, { conversation: { id: params.path.conversationId } })
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteConversationRoute = createObservabilityAIAssistantServerRoute({
|
||||
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
conversationId: t.string,
|
||||
}),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
},
|
||||
handler: async (resources): Promise<void> => {
|
||||
const { service, request, params } = resources;
|
||||
|
||||
const client = await service.getClient({ request });
|
||||
|
||||
if (!client) {
|
||||
throw notImplemented();
|
||||
}
|
||||
|
||||
return client.delete(params.path.conversationId);
|
||||
},
|
||||
});
|
||||
|
||||
export const conversationRoutes = {
|
||||
...getConversationRoute,
|
||||
...findConversationsRoute,
|
||||
...createConversationRoute,
|
||||
...updateConversationRoute,
|
||||
...deleteConversationRoute,
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { createServerRouteFactory } from '@kbn/server-route-repository';
|
||||
import type {
|
||||
ObservabilityAIAssistantRouteCreateOptions,
|
||||
ObservabilityAIAssistantRouteHandlerResources,
|
||||
} from './types';
|
||||
|
||||
export const createObservabilityAIAssistantServerRoute = createServerRouteFactory<
|
||||
ObservabilityAIAssistantRouteHandlerResources,
|
||||
ObservabilityAIAssistantRouteCreateOptions
|
||||
>();
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { chatRoutes } from './chat/route';
|
||||
import { conversationRoutes } from './conversations/route';
|
||||
|
||||
export function getGlobalObservabilityAIAssistantServerRouteRepository() {
|
||||
return {
|
||||
...chatRoutes,
|
||||
...conversationRoutes,
|
||||
};
|
||||
}
|
||||
|
||||
export type ObservabilityAIAssistantServerRouteRepository = ReturnType<
|
||||
typeof getGlobalObservabilityAIAssistantServerRouteRepository
|
||||
>;
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { CoreSetup } from '@kbn/core/server';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import { registerRoutes } from '@kbn/server-route-repository';
|
||||
import { getGlobalObservabilityAIAssistantServerRouteRepository } from './get_global_observability_ai_assistant_route_repository';
|
||||
import type { ObservabilityAIAssistantRouteHandlerResources } from './types';
|
||||
|
||||
export function registerServerRoutes({
|
||||
core,
|
||||
logger,
|
||||
dependencies,
|
||||
}: {
|
||||
core: CoreSetup;
|
||||
logger: Logger;
|
||||
dependencies: Omit<
|
||||
ObservabilityAIAssistantRouteHandlerResources,
|
||||
'request' | 'context' | 'logger' | 'params'
|
||||
>;
|
||||
}) {
|
||||
registerRoutes({
|
||||
core,
|
||||
logger,
|
||||
repository: getGlobalObservabilityAIAssistantServerRouteRepository(),
|
||||
dependencies,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import {
|
||||
Conversation,
|
||||
ConversationCreateRequest,
|
||||
ConversationRequestBase,
|
||||
ConversationUpdateRequest,
|
||||
Message,
|
||||
MessageRole,
|
||||
} from '../../common/types';
|
||||
|
||||
const serializeableRt = t.any;
|
||||
|
||||
export const messageRt: t.Type<Message> = t.type({
|
||||
'@timestamp': t.string,
|
||||
message: t.intersection([
|
||||
t.type({
|
||||
role: t.union([
|
||||
t.literal(MessageRole.System),
|
||||
t.literal(MessageRole.Assistant),
|
||||
t.literal(MessageRole.Event),
|
||||
t.literal(MessageRole.Function),
|
||||
t.literal(MessageRole.User),
|
||||
t.literal(MessageRole.Elastic),
|
||||
]),
|
||||
}),
|
||||
t.partial({
|
||||
content: t.string,
|
||||
name: t.string,
|
||||
function_call: t.intersection([
|
||||
t.type({
|
||||
name: t.string,
|
||||
trigger: t.union([
|
||||
t.literal(MessageRole.Assistant),
|
||||
t.literal(MessageRole.User),
|
||||
t.literal(MessageRole.Elastic),
|
||||
]),
|
||||
}),
|
||||
t.partial({
|
||||
args: serializeableRt,
|
||||
data: serializeableRt,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const baseConversationRt: t.Type<ConversationRequestBase> = t.type({
|
||||
'@timestamp': t.string,
|
||||
conversation: t.type({
|
||||
title: t.string,
|
||||
}),
|
||||
messages: t.array(messageRt),
|
||||
labels: t.record(t.string, t.string),
|
||||
numeric_labels: t.record(t.string, t.number),
|
||||
});
|
||||
|
||||
export const conversationCreateRt: t.Type<ConversationCreateRequest> = t.intersection([
|
||||
baseConversationRt,
|
||||
t.type({
|
||||
conversation: t.type({
|
||||
title: t.string,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const conversationUpdateRt: t.Type<ConversationUpdateRequest> = t.intersection([
|
||||
baseConversationRt,
|
||||
t.type({
|
||||
conversation: t.type({
|
||||
id: t.string,
|
||||
title: t.string,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const conversationRt: t.Type<Conversation> = t.intersection([
|
||||
baseConversationRt,
|
||||
t.type({
|
||||
user: t.intersection([t.type({ name: t.string }), t.partial({ id: t.string })]),
|
||||
namespace: t.string,
|
||||
conversation: t.type({
|
||||
id: t.string,
|
||||
last_updated: t.string,
|
||||
}),
|
||||
}),
|
||||
]);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { Logger } from '@kbn/logging';
|
||||
import type { KibanaRequest, RequestHandlerContext } from '@kbn/core/server';
|
||||
import type {
|
||||
ObservabilityAIAssistantPluginSetupDependencies,
|
||||
ObservabilityAIAssistantPluginStartDependencies,
|
||||
} from '../types';
|
||||
import type { IObservabilityAIAssistantService } from '../service/types';
|
||||
|
||||
export interface ObservabilityAIAssistantRouteHandlerResources {
|
||||
request: KibanaRequest;
|
||||
context: RequestHandlerContext;
|
||||
logger: Logger;
|
||||
service: IObservabilityAIAssistantService;
|
||||
plugins: {
|
||||
[key in keyof ObservabilityAIAssistantPluginSetupDependencies]: {
|
||||
setup: Required<ObservabilityAIAssistantPluginSetupDependencies>[key];
|
||||
};
|
||||
} & {
|
||||
[key in keyof ObservabilityAIAssistantPluginStartDependencies]: {
|
||||
start: () => Promise<Required<ObservabilityAIAssistantPluginStartDependencies>[key]>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ObservabilityAIAssistantRouteCreateOptions {
|
||||
options: {
|
||||
tags: Array<'access:ai_assistant'>;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* 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 { v4 } from 'uuid';
|
||||
|
||||
import type { ChatCompletionRequestMessage, CreateChatCompletionRequest } from 'openai';
|
||||
import type { IncomingMessage } from 'http';
|
||||
import type { ElasticsearchClient } from '@kbn/core/server';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { ActionsClient } from '@kbn/actions-plugin/server/actions_client';
|
||||
import type { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { internal, notFound } from '@hapi/boom';
|
||||
import { compact, merge, omit } from 'lodash';
|
||||
import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import {
|
||||
type Conversation,
|
||||
type ConversationCreateRequest,
|
||||
type ConversationUpdateRequest,
|
||||
type Message,
|
||||
MessageRole,
|
||||
} from '../../../common/types';
|
||||
import type {
|
||||
IObservabilityAIAssistantClient,
|
||||
ObservabilityAIAssistantResourceNames,
|
||||
} from '../types';
|
||||
|
||||
export class ObservabilityAIAssistantClient implements IObservabilityAIAssistantClient {
|
||||
constructor(
|
||||
private readonly dependencies: {
|
||||
actionsClient: PublicMethodsOf<ActionsClient>;
|
||||
namespace: string;
|
||||
esClient: ElasticsearchClient;
|
||||
resources: ObservabilityAIAssistantResourceNames;
|
||||
logger: Logger;
|
||||
user: {
|
||||
id?: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
) {}
|
||||
|
||||
private getAccessQuery() {
|
||||
return [
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
'user.name': this.dependencies.user.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
namespace: this.dependencies.namespace,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private getConversationWithMetaFields = async (
|
||||
conversationId: string
|
||||
): Promise<SearchHit<Conversation> | undefined> => {
|
||||
const response = await this.dependencies.esClient.search<Conversation>({
|
||||
index: this.dependencies.resources.aliases.conversations,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [...this.getAccessQuery(), { term: { 'conversation.id': conversationId } }],
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
terminate_after: 1,
|
||||
});
|
||||
|
||||
return response.hits.hits[0];
|
||||
};
|
||||
|
||||
private getConversationUpdateValues = (now: string) => {
|
||||
return {
|
||||
conversation: {
|
||||
last_updated: now,
|
||||
},
|
||||
user: this.dependencies.user,
|
||||
namespace: this.dependencies.namespace,
|
||||
};
|
||||
};
|
||||
|
||||
get = async (conversationId: string): Promise<Conversation | undefined> => {
|
||||
return (await this.getConversationWithMetaFields(conversationId))?._source;
|
||||
};
|
||||
|
||||
delete = async (conversationId: string): Promise<void> => {
|
||||
const conversation = await this.getConversationWithMetaFields(conversationId);
|
||||
|
||||
if (!conversation) {
|
||||
throw notFound();
|
||||
}
|
||||
|
||||
await this.dependencies.esClient.delete({
|
||||
id: conversation._id,
|
||||
index: conversation._index,
|
||||
});
|
||||
};
|
||||
|
||||
chat = async ({
|
||||
messages,
|
||||
connectorId,
|
||||
}: {
|
||||
messages: Message[];
|
||||
connectorId: string;
|
||||
}): Promise<IncomingMessage> => {
|
||||
const messagesForOpenAI: ChatCompletionRequestMessage[] = compact(
|
||||
messages.map((message) => {
|
||||
if (message.message.role === MessageRole.Event) {
|
||||
return undefined;
|
||||
}
|
||||
const role =
|
||||
message.message.role === MessageRole.Elastic ? MessageRole.User : message.message.role;
|
||||
|
||||
return {
|
||||
role,
|
||||
content: message.message.content,
|
||||
function_call: omit(message.message.function_call, 'trigger'),
|
||||
name: message.message.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const request: CreateChatCompletionRequest = {
|
||||
model: 'gpt-4',
|
||||
messages: messagesForOpenAI,
|
||||
stream: true,
|
||||
};
|
||||
|
||||
const executeResult = await this.dependencies.actionsClient.execute({
|
||||
actionId: connectorId,
|
||||
params: {
|
||||
subAction: 'stream',
|
||||
subActionParams: JSON.stringify(request),
|
||||
},
|
||||
});
|
||||
|
||||
if (executeResult.status === 'error') {
|
||||
throw internal(`${executeResult?.message} - ${executeResult?.serviceMessage}`);
|
||||
}
|
||||
|
||||
return executeResult.data as IncomingMessage;
|
||||
};
|
||||
|
||||
find = async (options?: { query?: string }): Promise<{ conversations: Conversation[] }> => {
|
||||
const response = await this.dependencies.esClient.search<Conversation>({
|
||||
index: this.dependencies.resources.aliases.conversations,
|
||||
allow_no_indices: true,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [...this.getAccessQuery()],
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
size: 100,
|
||||
});
|
||||
|
||||
return {
|
||||
conversations: response.hits.hits.map((hit) => hit._source!),
|
||||
};
|
||||
};
|
||||
|
||||
update = async (conversation: ConversationUpdateRequest): Promise<Conversation> => {
|
||||
const document = await this.getConversationWithMetaFields(conversation.conversation.id);
|
||||
|
||||
if (!document) {
|
||||
throw notFound();
|
||||
}
|
||||
|
||||
const updatedConversation: Conversation = merge(
|
||||
{},
|
||||
conversation,
|
||||
this.getConversationUpdateValues(new Date().toISOString())
|
||||
);
|
||||
|
||||
await this.dependencies.esClient.update({
|
||||
id: document._id,
|
||||
index: document._index,
|
||||
doc: updatedConversation,
|
||||
});
|
||||
|
||||
return updatedConversation;
|
||||
};
|
||||
|
||||
create = async (conversation: ConversationCreateRequest): Promise<Conversation> => {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const createdConversation: Conversation = merge(
|
||||
{},
|
||||
conversation,
|
||||
{
|
||||
'@timestamp': now,
|
||||
conversation: { id: v4() },
|
||||
},
|
||||
this.getConversationUpdateValues(now)
|
||||
);
|
||||
|
||||
await this.dependencies.esClient.index({
|
||||
index: this.dependencies.resources.aliases.conversations,
|
||||
document: createdConversation,
|
||||
});
|
||||
|
||||
return createdConversation;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { ClusterComponentTemplate } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
const keyword = {
|
||||
type: 'keyword' as const,
|
||||
ignore_above: 1024,
|
||||
};
|
||||
|
||||
const text = {
|
||||
type: 'text' as const,
|
||||
};
|
||||
|
||||
const date = {
|
||||
type: 'date' as const,
|
||||
};
|
||||
|
||||
const dynamic = {
|
||||
type: 'object' as const,
|
||||
dynamic: true,
|
||||
};
|
||||
|
||||
export const conversationComponentTemplate: ClusterComponentTemplate['component_template']['template'] =
|
||||
{
|
||||
mappings: {
|
||||
dynamic_templates: [
|
||||
{
|
||||
numeric_labels: {
|
||||
path_match: 'numeric_labels.*',
|
||||
mapping: {
|
||||
scaling_factor: 1000000,
|
||||
type: 'scaled_float',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
dynamic: false,
|
||||
properties: {
|
||||
'@timestamp': date,
|
||||
labels: dynamic,
|
||||
numeric_labels: dynamic,
|
||||
user: {
|
||||
properties: {
|
||||
id: keyword,
|
||||
name: keyword,
|
||||
},
|
||||
},
|
||||
conversation: {
|
||||
properties: {
|
||||
id: keyword,
|
||||
title: text,
|
||||
last_updated: date,
|
||||
},
|
||||
},
|
||||
namespace: keyword,
|
||||
messages: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'@timestamp': date,
|
||||
message: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: text,
|
||||
role: keyword,
|
||||
data: {
|
||||
type: 'object',
|
||||
enabled: false,
|
||||
},
|
||||
function_call: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: keyword,
|
||||
args: {
|
||||
type: 'object',
|
||||
enabled: false,
|
||||
},
|
||||
trigger: keyword,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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 stringify from 'json-stable-stringify';
|
||||
import type { CoreSetup, CoreStart, KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { once } from 'lodash';
|
||||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import * as Boom from '@hapi/boom';
|
||||
import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server/plugin';
|
||||
import { getSpaceIdFromPath } from '@kbn/spaces-plugin/common';
|
||||
import type { ObservabilityAIAssistantResourceNames } from './types';
|
||||
import { conversationComponentTemplate } from './conversation_component_template';
|
||||
import type { IObservabilityAIAssistantClient, IObservabilityAIAssistantService } from './types';
|
||||
import { ObservabilityAIAssistantClient } from './client';
|
||||
|
||||
function getResourceName(resource: string) {
|
||||
return `.kibana-observability-ai-assistant-${resource}`;
|
||||
}
|
||||
|
||||
export class ObservabilityAIAssistantService implements IObservabilityAIAssistantService {
|
||||
private readonly core: CoreSetup;
|
||||
private readonly logger: Logger;
|
||||
|
||||
private readonly resourceNames: ObservabilityAIAssistantResourceNames = {
|
||||
componentTemplate: {
|
||||
conversations: getResourceName('component-template-conversations'),
|
||||
},
|
||||
aliases: {
|
||||
conversations: getResourceName('conversations'),
|
||||
},
|
||||
indexPatterns: {
|
||||
conversations: getResourceName('conversations*'),
|
||||
},
|
||||
indexTemplate: {
|
||||
conversations: getResourceName('index-template-conversations'),
|
||||
},
|
||||
ilmPolicy: {
|
||||
conversations: getResourceName('ilm-policy-conversations'),
|
||||
},
|
||||
};
|
||||
|
||||
constructor({ logger, core }: { logger: Logger; core: CoreSetup }) {
|
||||
this.core = core;
|
||||
this.logger = logger;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init = once(async () => {
|
||||
try {
|
||||
const [coreStart] = await this.core.getStartServices();
|
||||
|
||||
const esClient = coreStart.elasticsearch.client.asInternalUser;
|
||||
|
||||
const fnv1a = await import('@sindresorhus/fnv1a');
|
||||
|
||||
const versionHash = fnv1a.default(stringify(conversationComponentTemplate), { size: 64 });
|
||||
|
||||
await esClient.cluster.putComponentTemplate({
|
||||
create: false,
|
||||
name: this.resourceNames.componentTemplate.conversations,
|
||||
template: {
|
||||
...conversationComponentTemplate,
|
||||
mappings: {
|
||||
_meta: {
|
||||
version: versionHash,
|
||||
},
|
||||
...conversationComponentTemplate.mappings,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await esClient.ilm.putLifecycle({
|
||||
name: this.resourceNames.ilmPolicy.conversations,
|
||||
policy: {
|
||||
phases: {
|
||||
hot: {
|
||||
min_age: '0s',
|
||||
actions: {
|
||||
rollover: {
|
||||
max_age: '90d',
|
||||
max_primary_shard_size: '50gb',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await esClient.indices.putIndexTemplate({
|
||||
name: this.resourceNames.indexTemplate.conversations,
|
||||
composed_of: [this.resourceNames.componentTemplate.conversations],
|
||||
create: false,
|
||||
index_patterns: [this.resourceNames.indexPatterns.conversations],
|
||||
template: {
|
||||
settings: {
|
||||
number_of_shards: 1,
|
||||
auto_expand_replicas: '0-1',
|
||||
refresh_interval: '1s',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const aliasName = this.resourceNames.aliases.conversations;
|
||||
|
||||
const aliasExists = await esClient.indices.existsAlias({
|
||||
name: aliasName,
|
||||
});
|
||||
|
||||
if (!aliasExists) {
|
||||
const firstIndexName = `${this.resourceNames.aliases.conversations}-000001`;
|
||||
await esClient.indices.create({
|
||||
index: firstIndexName,
|
||||
aliases: {
|
||||
[aliasName]: {
|
||||
is_write_index: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const indicesForAlias = await esClient.indices.get({
|
||||
index: aliasName,
|
||||
});
|
||||
|
||||
const writeIndexName = Object.keys(indicesForAlias).find((indexName) => {
|
||||
if (indicesForAlias[indexName]!.aliases?.[aliasName].is_write_index) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!writeIndexName) {
|
||||
throw new Error(`Expected write index for ${aliasName}, but none was found`);
|
||||
}
|
||||
|
||||
const writeIndex = indicesForAlias[writeIndexName];
|
||||
|
||||
if (writeIndex.mappings?._meta?.version !== versionHash) {
|
||||
await esClient.indices.rollover({
|
||||
alias: aliasName,
|
||||
conditions: {
|
||||
min_docs: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to initialize CoPilotService: ${error.message}`);
|
||||
this.logger.debug(error);
|
||||
}
|
||||
});
|
||||
|
||||
async getClient({
|
||||
request,
|
||||
}: {
|
||||
request: KibanaRequest;
|
||||
}): Promise<IObservabilityAIAssistantClient> {
|
||||
const [_, [coreStart, { security, actions }]] = await Promise.all([
|
||||
this.init(),
|
||||
this.core.getStartServices() as Promise<
|
||||
[CoreStart, { security: SecurityPluginStart; actions: ActionsPluginStart }, unknown]
|
||||
>,
|
||||
]);
|
||||
|
||||
const user = security.authc.getCurrentUser(request);
|
||||
|
||||
if (!user) {
|
||||
throw Boom.forbidden(`User not found for current request`);
|
||||
}
|
||||
|
||||
const basePath = coreStart.http.basePath.get(request);
|
||||
|
||||
const { spaceId } = getSpaceIdFromPath(basePath, coreStart.http.basePath.serverBasePath);
|
||||
|
||||
return new ObservabilityAIAssistantClient({
|
||||
actionsClient: await actions.getActionsClientWithRequest(request),
|
||||
namespace: spaceId,
|
||||
esClient: coreStart.elasticsearch.client.asInternalUser,
|
||||
resources: this.resourceNames,
|
||||
logger: this.logger,
|
||||
user: {
|
||||
id: user.profile_uid,
|
||||
name: user.username,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { IncomingMessage } from 'http';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
import {
|
||||
Conversation,
|
||||
ConversationCreateRequest,
|
||||
ConversationUpdateRequest,
|
||||
Message,
|
||||
} from '../../common/types';
|
||||
|
||||
export interface IObservabilityAIAssistantClient {
|
||||
chat: (options: { messages: Message[]; connectorId: string }) => Promise<IncomingMessage>;
|
||||
get: (conversationId: string) => void;
|
||||
find: (options?: { query?: string }) => Promise<{ conversations: Conversation[] }>;
|
||||
create: (conversation: ConversationCreateRequest) => Promise<Conversation>;
|
||||
update: (conversation: ConversationUpdateRequest) => Promise<Conversation>;
|
||||
delete: (conversationId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface IObservabilityAIAssistantService {
|
||||
getClient: (options: {
|
||||
request: KibanaRequest;
|
||||
}) => Promise<IObservabilityAIAssistantClient | undefined>;
|
||||
}
|
||||
|
||||
export interface ObservabilityAIAssistantResourceNames {
|
||||
componentTemplate: {
|
||||
conversations: string;
|
||||
};
|
||||
indexTemplate: {
|
||||
conversations: string;
|
||||
};
|
||||
ilmPolicy: {
|
||||
conversations: string;
|
||||
};
|
||||
aliases: {
|
||||
conversations: string;
|
||||
};
|
||||
indexPatterns: {
|
||||
conversations: string;
|
||||
};
|
||||
}
|
|
@ -5,8 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PluginSetupContract, PluginStartContract } from '@kbn/actions-plugin/server';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface*/
|
||||
export interface ObservabilityAIAssistantPluginStart {}
|
||||
export interface ObservabilityAIAssistantPluginSetup {}
|
||||
export interface ObservabilityAIAssistantPluginSetupDependencies {}
|
||||
export interface ObservabilityAIAssistantPluginStartDependencies {}
|
||||
export interface ObservabilityAIAssistantPluginSetupDependencies {
|
||||
actions: PluginSetupContract;
|
||||
}
|
||||
export interface ObservabilityAIAssistantPluginStartDependencies {
|
||||
actions: PluginStartContract;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,14 @@
|
|||
"server/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core"
|
||||
"@kbn/core",
|
||||
"@kbn/actions-plugin",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/server-route-repository",
|
||||
"@kbn/logging",
|
||||
"@kbn/triggers-actions-ui-plugin",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/security-plugin"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -6847,6 +6847,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
|
||||
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
|
||||
|
||||
"@sindresorhus/fnv1a@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/fnv1a/-/fnv1a-3.0.0.tgz#e8ce2e7c7738ec8c354867d38e3bfcde622b87ca"
|
||||
integrity sha512-M6pmbdZqAryzjZ4ELAzrdCMoMZk5lH/fshKrapfSeXdf2W+GDqZvPmfXaNTZp43//FVbSwkTPwpEMnehSyskkQ==
|
||||
|
||||
"@sindresorhus/is@^0.14.0":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||
|
@ -22418,10 +22423,10 @@ open@^8.0.9, open@^8.4.0:
|
|||
is-docker "^2.1.1"
|
||||
is-wsl "^2.2.0"
|
||||
|
||||
openai@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/openai/-/openai-3.2.1.tgz#1fa35bdf979cbde8453b43f2dd3a7d401ee40866"
|
||||
integrity sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==
|
||||
openai@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/openai/-/openai-3.3.0.tgz#a6408016ad0945738e1febf43f2fccca83a3f532"
|
||||
integrity sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==
|
||||
dependencies:
|
||||
axios "^0.26.0"
|
||||
form-data "^4.0.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue