diff --git a/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts b/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts index a795b0eedc2ac5..66f29afcd5ef1f 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/sub_action_connector.ts @@ -11,6 +11,8 @@ import { Logger } from '@kbn/logging'; import axios, { AxiosInstance, AxiosResponse, AxiosError, AxiosRequestHeaders } from 'axios'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { finished } from 'stream/promises'; +import { IncomingMessage } from 'http'; import { assertURL } from './helpers/validators'; import { ActionsConfigurationUtilities } from '../actions_config'; import { SubAction, SubActionRequestParams } from './types'; @@ -140,6 +142,25 @@ export abstract class SubActionConnector { `Request to external service failed. Connector Id: ${this.connector.id}. Connector type: ${this.connector.type}. Method: ${error.config.method}. URL: ${error.config.url}` ); + let responseBody = ''; + + // The error response body may also be a stream, e.g. for the GenAI connector + if (error.response?.config?.responseType === 'stream' && error.response?.data) { + try { + const incomingMessage = error.response.data as IncomingMessage; + + incomingMessage.on('data', (chunk) => { + responseBody += chunk.toString(); + }); + + await finished(incomingMessage); + + error.response.data = JSON.parse(responseBody); + } catch { + // the response body is a nice to have, no worries if it fails + } + } + const errorMessage = `Status code: ${ error.status ?? error.response?.status }. Message: ${this.getResponseErrorMessage(error)}`; diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts index afca8f55f0e17f..aabe8f898cc928 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts @@ -151,6 +151,40 @@ export default function ApiTest({ getService }: FtrProviderContext) { ]); }); + it('returns a useful error if the request fails', async () => { + requestHandler = (request, response) => { + response.writeHead(400, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + + response.write( + JSON.stringify({ + error: { + code: 'context_length_exceeded', + message: + "This model's maximum context length is 8192 tokens. However, your messages resulted in 11036 tokens. Please reduce the length of the messages.", + param: 'messages', + type: 'invalid_request_error', + }, + }) + ); + + response.end(); + }; + + const response = await supertest.post(CHAT_API_URL).set('kbn-xsrf', 'foo').send({ + messages, + connectorId, + functions: [], + }); + + expect(response.body.message).to.contain( + `400 - Bad Request - This model's maximum context length is 8192 tokens. However, your messages resulted in 11036 tokens. Please reduce the length of the messages.` + ); + }); + after(async () => { requestHandler = () => {}; await supertest