Skip to content

Commit

Permalink
Merge branch 'main' into fix/report_flyout_wordwrap
Browse files Browse the repository at this point in the history
  • Loading branch information
kowalczyk-krzysztof authored Oct 16, 2024
2 parents 42c37dd + e34876a commit 45f8a8a
Show file tree
Hide file tree
Showing 66 changed files with 703 additions and 214 deletions.
5 changes: 4 additions & 1 deletion x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { SimulatedFunctionCallingCallout } from './simulated_function_calling_ca
import { WelcomeMessage } from './welcome_message';
import { useLicense } from '../hooks/use_license';
import { PromptEditor } from '../prompt_editor/prompt_editor';
import { deserializeMessage } from '../utils/deserialize_message';

const fullHeightClassName = css`
height: 100%;
Expand Down Expand Up @@ -226,9 +227,11 @@ export function ChatBody({
});

const handleCopyConversation = () => {
const deserializedMessages = (conversation.value?.messages ?? messages).map(deserializeMessage);

const content = JSON.stringify({
title: initialTitle,
messages: conversation.value?.messages ?? messages,
messages: deserializedMessages,
});

navigator.clipboard?.writeText(content || '');
Expand Down
118 changes: 118 additions & 0 deletions x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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 { cloneDeep } from 'lodash';
import { Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/common';
import { deserializeMessage } from './deserialize_message';
import { safeJsonParse } from './safe_json_parse';

jest.mock('lodash', () => ({
cloneDeep: jest.fn(),
}));

jest.mock('./safe_json_parse', () => ({
safeJsonParse: jest.fn((value) => {
try {
return JSON.parse(value);
} catch {
return value;
}
}),
}));

describe('deserializeMessage', () => {
const baseMessage: Message = {
'@timestamp': '2024-10-15T00:00:00Z',
message: {
role: MessageRole.User,
content: 'This is a message',
},
};

beforeEach(() => {
(cloneDeep as jest.Mock).mockImplementation((obj) => JSON.parse(JSON.stringify(obj)));
});

it('should clone the original message', () => {
const message = { ...baseMessage };
deserializeMessage(message);

expect(cloneDeep).toHaveBeenCalledWith(message);
});

it('should deserialize function_call.arguments if it is a string', () => {
const messageWithFunctionCall: Message = {
...baseMessage,
message: {
...baseMessage.message,
function_call: {
name: 'testFunction',
arguments: '{"key": "value"}',
trigger: MessageRole.Assistant,
},
},
};

const result = deserializeMessage(messageWithFunctionCall);

expect(safeJsonParse).toHaveBeenCalledWith('{"key": "value"}');
expect(result.message.function_call!.arguments).toEqual({ key: 'value' });
});

it('should deserialize message.content if it is a string', () => {
const messageWithContent: Message = {
...baseMessage,
message: {
...baseMessage.message,
name: 'testMessage',
content: '{"key": "value"}',
},
};

const result = deserializeMessage(messageWithContent);

expect(safeJsonParse).toHaveBeenCalledWith('{"key": "value"}');
expect(result.message.content).toEqual({ key: 'value' });
});

it('should deserialize message.data if it is a string', () => {
const messageWithData: Message = {
...baseMessage,
message: {
...baseMessage.message,
name: 'testMessage',
data: '{"key": "value"}',
},
};

const result = deserializeMessage(messageWithData);

expect(safeJsonParse).toHaveBeenCalledWith('{"key": "value"}');
expect(result.message.data).toEqual({ key: 'value' });
});

it('should return the copied message as is if no deserialization is needed', () => {
const messageWithoutSerialization: Message = {
...baseMessage,
message: {
...baseMessage.message,
function_call: {
name: 'testFunction',
arguments: '',
trigger: MessageRole.Assistant,
},
content: '',
},
};

const result = deserializeMessage(messageWithoutSerialization);

expect(result.message.function_call!.name).toEqual('testFunction');
expect(result.message.function_call!.arguments).toEqual('');
expect(result.message.content).toEqual('');
});
});
35 changes: 35 additions & 0 deletions x-pack/packages/kbn-ai-assistant/src/utils/deserialize_message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { cloneDeep } from 'lodash';
import type { Message } from '@kbn/observability-ai-assistant-plugin/common';
import { safeJsonParse } from './safe_json_parse';

export const deserializeMessage = (message: Message): Message => {
const copiedMessage = cloneDeep(message);

if (
copiedMessage.message.function_call?.arguments &&
typeof copiedMessage.message.function_call?.arguments === 'string'
) {
copiedMessage.message.function_call.arguments = safeJsonParse(
copiedMessage.message.function_call.arguments ?? '{}'
);
}

if (copiedMessage.message.name) {
if (copiedMessage.message.content && typeof copiedMessage.message.content === 'string') {
copiedMessage.message.content = safeJsonParse(copiedMessage.message.content);
}

if (copiedMessage.message.data && typeof copiedMessage.message.data === 'string') {
copiedMessage.message.data = safeJsonParse(copiedMessage.message.data);
}
}

return copiedMessage;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export {
executeConnectorRequestParamsSchema,
executeConnectorRequestBodySchema,
} from './schemas/latest';
export type { ExecuteConnectorRequestParams, ExecuteConnectorRequestBody } from './types/latest';

export {
executeConnectorRequestParamsSchema as executeConnectorRequestParamsSchemaV1,
executeConnectorRequestBodySchema as executeConnectorRequestBodySchemaV1,
} from './schemas/v1';
export type {
ExecuteConnectorRequestParams as ExecuteConnectorRequestParamsV1,
ExecuteConnectorRequestBody as ExecuteConnectorRequestBodyV1,
} from './types/v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './v1';
Original file line number Diff line number Diff line change
@@ -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 { schema } from '@kbn/config-schema';

export const executeConnectorRequestParamsSchema = schema.object({
id: schema.string({
meta: {
description: 'An identifier for the connector.',
},
}),
});

export const executeConnectorRequestBodySchema = schema.object({
params: schema.recordOf(schema.string(), schema.any()),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import { executeConnectorRequestParamsSchemaV1, executeConnectorRequestBodySchemaV1 } from '..';

export type ExecuteConnectorRequestParams = TypeOf<typeof executeConnectorRequestParamsSchemaV1>;
export type ExecuteConnectorRequestBody = TypeOf<typeof executeConnectorRequestBodySchemaV1>;
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@
*/

// Latest
export type { ConnectorResponse, AllConnectorsResponse } from './types/latest';
export type {
ConnectorResponse,
AllConnectorsResponse,
ConnectorExecuteResponse,
} from './types/latest';
export {
connectorResponseSchema,
allConnectorsResponseSchema,
connectorTypesResponseSchema,
connectorExecuteResponseSchema,
} from './schemas/latest';

// v1
export type {
ConnectorResponse as ConnectorResponseV1,
AllConnectorsResponse as AllConnectorsResponseV1,
ConnectorTypesResponse as ConnectorTypesResponseV1,
ConnectorExecuteResponse as ConnectorExecuteResponseV1,
} from './types/v1';
export {
connectorResponseSchema as connectorResponseSchemaV1,
allConnectorsResponseSchema as connectorWithExtraFindDataSchemaV1,
connectorTypesResponseSchema as connectorTypesResponseSchemaV1,
connectorExecuteResponseSchema as connectorExecuteResponseSchemaV1,
} from './schemas/v1';
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
export { connectorResponseSchema } from './v1';
export { allConnectorsResponseSchema } from './v1';
export { connectorTypesResponseSchema } from './v1';
export { connectorExecuteResponseSchema } from './v1';
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,55 @@ export const connectorTypesResponseSchema = schema.object({
meta: { description: 'Indicates whether the action is a system action.' },
}),
});

export const connectorExecuteResponseSchema = schema.object({
connector_id: schema.string({
meta: {
description: 'The identifier for the connector.',
},
}),
status: schema.oneOf([schema.literal('ok'), schema.literal('error')], {
meta: {
description: 'The outcome of the connector execution.',
},
}),
message: schema.maybe(
schema.string({
meta: {
description: 'The connector execution error message.',
},
})
),
service_message: schema.maybe(
schema.string({
meta: {
description: 'An error message that contains additional details.',
},
})
),
data: schema.maybe(
schema.any({
meta: {
description: 'The connector execution data.',
},
})
),
retry: schema.maybe(
schema.nullable(
schema.oneOf([schema.boolean(), schema.string()], {
meta: {
description:
'When the status is error, identifies whether the connector execution will retry .',
},
})
)
),
errorSource: schema.maybe(
schema.oneOf([schema.literal('user'), schema.literal('framework')], {
meta: {
description:
'When the status is error, identifies whether the error is a framework error or a user error.',
},
})
),
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
connectorResponseSchemaV1,
connectorTypesResponseSchemaV1,
allConnectorsResponseSchema,
connectorExecuteResponseSchema,
} from '..';

type ConnectorResponseSchemaType = TypeOf<typeof connectorResponseSchemaV1>;
Expand Down Expand Up @@ -41,3 +42,14 @@ export interface ConnectorTypesResponse {
supported_feature_ids: ConnectorTypesResponseSchemaType['supported_feature_ids'];
is_system_action_type: ConnectorTypesResponseSchemaType['is_system_action_type'];
}

type ConnectorExecuteResponseSchemaType = TypeOf<typeof connectorExecuteResponseSchema>;
export interface ConnectorExecuteResponse {
connector_id: ConnectorExecuteResponseSchemaType['connector_id'];
status: ConnectorExecuteResponseSchemaType['status'];
message?: ConnectorExecuteResponseSchemaType['message'];
service_message?: ConnectorExecuteResponseSchemaType['service_message'];
data?: ConnectorExecuteResponseSchemaType['data'];
retry?: ConnectorExecuteResponseSchemaType['retry'];
errorSource?: ConnectorExecuteResponseSchemaType['errorSource'];
}
Loading

0 comments on commit 45f8a8a

Please sign in to comment.