Skip to content

Commit 1531849

Browse files
authored
[Stack Connectors] Add organizationId and projectId OpenAI headers, along with arbitrary headers (#213117)
1 parent 5b8fd8f commit 1531849

File tree

12 files changed

+423
-25
lines changed

12 files changed

+423
-25
lines changed

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42073,7 +42073,6 @@
4207342073
"xpack.stackConnectors.components.genAi.connectorTypeTitle": "OpenAI",
4207442074
"xpack.stackConnectors.components.genAi.dashboardLink": "Affichez le tableau de bord d'utilisation de {apiProvider} pour le connecteur \"{ connectorName }\"",
4207542075
"xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "Modèle par défaut",
42076-
"xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "Si une requête ne comprend pas de modèle, le modèle par défaut est utilisé.",
4207742076
"xpack.stackConnectors.components.genAi.documentation": "documentation",
4207842077
"xpack.stackConnectors.components.genAi.error.dashboardApiError": "Erreur lors de la recherche du tableau de bord d'utilisation des tokens {apiProvider}.",
4207942078
"xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "Un fournisseur d’API est nécessaire.",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41926,7 +41926,6 @@
4192641926
"xpack.stackConnectors.components.genAi.connectorTypeTitle": "OpenAI",
4192741927
"xpack.stackConnectors.components.genAi.dashboardLink": "\"{ connectorName }\"コネクターの{apiProvider}使用状況ダッシュボードを表示",
4192841928
"xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "デフォルトモデル",
41929-
"xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "リクエストにモデルが含まれていない場合、デフォルトが使われます。",
4193041929
"xpack.stackConnectors.components.genAi.documentation": "ドキュメンテーション",
4193141930
"xpack.stackConnectors.components.genAi.error.dashboardApiError": "{apiProvider}トークン使用状況ダッシュボードの検索エラー。",
4193241931
"xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "APIプロバイダーは必須です。",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41303,7 +41303,6 @@
4130341303
"xpack.stackConnectors.components.genAi.bodyFieldLabel": "正文",
4130441304
"xpack.stackConnectors.components.genAi.connectorTypeTitle": "OpenAI",
4130541305
"xpack.stackConnectors.components.genAi.defaultModelTextFieldLabel": "默认模型",
41306-
"xpack.stackConnectors.components.genAi.defaultModelTooltipContent": "如果请求不包含模型,它将使用默认值。",
4130741306
"xpack.stackConnectors.components.genAi.documentation": "文档",
4130841307
"xpack.stackConnectors.components.genAi.error.dashboardApiError": "查找 {apiProvider} 令牌使用情况仪表板时出错。",
4130941308
"xpack.stackConnectors.components.genAi.error.requiredApiProviderText": "'API 提供商'必填。",

x-pack/platform/plugins/shared/stack_connectors/common/experimental_features.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const allowedExperimentalValues = Object.freeze({
1818
inferenceConnectorOff: false,
1919
crowdstrikeConnectorRTROn: true,
2020
microsoftDefenderEndpointOn: true,
21+
openAIAdditionalHeadersOn: false,
2122
});
2223

2324
export type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;

x-pack/platform/plugins/shared/stack_connectors/common/openai/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export const ConfigSchema = schema.oneOf([
2323
schema.object({
2424
apiProvider: schema.oneOf([schema.literal(OpenAiProviderType.OpenAi)]),
2525
apiUrl: schema.string(),
26+
organizationId: schema.maybe(schema.string()),
27+
projectId: schema.maybe(schema.string()),
2628
defaultModel: schema.string({ defaultValue: DEFAULT_OPENAI_MODEL }),
2729
headers: schema.maybe(schema.recordOf(schema.string(), schema.string())),
2830
}),

x-pack/platform/plugins/shared/stack_connectors/public/connector_types/openai/connector.test.tsx

Lines changed: 161 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import React from 'react';
99
import ConnectorFields from './connector';
1010
import { ConnectorFormTestProvider } from '../lib/test_utils';
11-
import { act, fireEvent, render, waitFor } from '@testing-library/react';
11+
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
1212
import userEvent from '@testing-library/user-event';
1313
import { DEFAULT_OPENAI_MODEL, OpenAiProviderType } from '../../../common/openai/constants';
1414
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
1515
import { useGetDashboard } from '../lib/gen_ai/use_get_dashboard';
1616
import { createStartServicesMock } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana/kibana_react.mock';
17+
import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
18+
import { experimentalFeaturesMock } from '../../mocks';
1719

1820
const mockUseKibanaReturnValue = createStartServicesMock();
1921
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana', () => ({
@@ -38,6 +40,9 @@ const openAiConnector = {
3840
secrets: {
3941
apiKey: 'thats-a-nice-looking-key',
4042
},
43+
__internal__: {
44+
hasHeaders: false,
45+
},
4146
isDeprecated: false,
4247
};
4348
const azureConnector = {
@@ -65,6 +70,12 @@ const otherOpenAiConnector = {
6570
const navigateToUrl = jest.fn();
6671

6772
describe('ConnectorFields renders', () => {
73+
beforeAll(() => {
74+
ExperimentalFeaturesService.init({
75+
// @ts-ignore force enable for testing
76+
experimentalFeatures: { ...experimentalFeaturesMock, openAIAdditionalHeadersOn: true },
77+
});
78+
});
6879
beforeEach(() => {
6980
jest.clearAllMocks();
7081
useKibanaMock().services.application.navigateToUrl = navigateToUrl;
@@ -84,12 +95,14 @@ describe('ConnectorFields renders', () => {
8495
expect(getAllByTestId('config.apiProvider-select')[0]).toHaveValue(
8596
openAiConnector.config.apiProvider
8697
);
98+
expect(getAllByTestId('config.organizationId-input')[0]).toBeInTheDocument();
99+
expect(getAllByTestId('config.projectId-input')[0]).toBeInTheDocument();
87100
expect(getAllByTestId('open-ai-api-doc')[0]).toBeInTheDocument();
88101
expect(getAllByTestId('open-ai-api-keys-doc')[0]).toBeInTheDocument();
89102
});
90103

91104
test('azure ai connector fields are rendered', async () => {
92-
const { getAllByTestId } = render(
105+
const { getAllByTestId, queryByTestId } = render(
93106
<ConnectorFormTestProvider connector={azureConnector}>
94107
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
95108
</ConnectorFormTestProvider>
@@ -102,10 +115,12 @@ describe('ConnectorFields renders', () => {
102115
);
103116
expect(getAllByTestId('azure-ai-api-doc')[0]).toBeInTheDocument();
104117
expect(getAllByTestId('azure-ai-api-keys-doc')[0]).toBeInTheDocument();
118+
expect(queryByTestId('config.organizationId-input')).not.toBeInTheDocument();
119+
expect(queryByTestId('config.projectId-input')).not.toBeInTheDocument();
105120
});
106121

107122
test('other open ai connector fields are rendered', async () => {
108-
const { getAllByTestId } = render(
123+
const { getAllByTestId, queryByTestId } = render(
109124
<ConnectorFormTestProvider connector={otherOpenAiConnector}>
110125
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
111126
</ConnectorFormTestProvider>
@@ -120,6 +135,149 @@ describe('ConnectorFields renders', () => {
120135
);
121136
expect(getAllByTestId('other-ai-api-doc')[0]).toBeInTheDocument();
122137
expect(getAllByTestId('other-ai-api-keys-doc')[0]).toBeInTheDocument();
138+
expect(queryByTestId('config.organizationId-input')).not.toBeInTheDocument();
139+
expect(queryByTestId('config.projectId-input')).not.toBeInTheDocument();
140+
});
141+
describe('Headers', () => {
142+
it('toggles headers as expected', async () => {
143+
const testFormData = {
144+
actionTypeId: '.gen-ai',
145+
name: 'OpenAI',
146+
id: '123',
147+
config: {
148+
apiUrl: 'https://openaiurl.com',
149+
apiProvider: OpenAiProviderType.OpenAi,
150+
defaultModel: DEFAULT_OPENAI_MODEL,
151+
},
152+
secrets: {
153+
apiKey: 'thats-a-nice-looking-key',
154+
},
155+
isDeprecated: false,
156+
__internal__: {
157+
hasHeaders: false,
158+
},
159+
};
160+
render(
161+
<ConnectorFormTestProvider connector={testFormData}>
162+
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
163+
</ConnectorFormTestProvider>
164+
);
165+
166+
const headersToggle = await screen.findByTestId('openAIViewHeadersSwitch');
167+
168+
expect(headersToggle).toBeInTheDocument();
169+
170+
await userEvent.click(headersToggle);
171+
172+
expect(await screen.findByTestId('openAIHeaderText')).toBeInTheDocument();
173+
expect(await screen.findByTestId('openAIHeadersKeyInput')).toBeInTheDocument();
174+
expect(await screen.findByTestId('openAIHeadersValueInput')).toBeInTheDocument();
175+
expect(await screen.findByTestId('openAIAddHeaderButton')).toBeInTheDocument();
176+
});
177+
it('succeeds without headers', async () => {
178+
const testFormData = {
179+
actionTypeId: '.gen-ai',
180+
name: 'OpenAI',
181+
id: '123',
182+
config: {
183+
apiUrl: 'https://openaiurl.com',
184+
apiProvider: OpenAiProviderType.OpenAi,
185+
defaultModel: DEFAULT_OPENAI_MODEL,
186+
},
187+
secrets: {
188+
apiKey: 'thats-a-nice-looking-key',
189+
},
190+
isDeprecated: false,
191+
__internal__: {
192+
hasHeaders: false,
193+
},
194+
};
195+
const onSubmit = jest.fn();
196+
render(
197+
<ConnectorFormTestProvider connector={testFormData} onSubmit={onSubmit}>
198+
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
199+
</ConnectorFormTestProvider>
200+
);
201+
202+
await userEvent.click(await screen.findByTestId('form-test-provide-submit'));
203+
await waitFor(() => {
204+
expect(onSubmit).toHaveBeenCalledWith({
205+
data: {
206+
actionTypeId: '.gen-ai',
207+
name: 'OpenAI',
208+
id: '123',
209+
isDeprecated: false,
210+
config: {
211+
apiUrl: 'https://openaiurl.com',
212+
apiProvider: OpenAiProviderType.OpenAi,
213+
defaultModel: DEFAULT_OPENAI_MODEL,
214+
},
215+
secrets: {
216+
apiKey: 'thats-a-nice-looking-key',
217+
},
218+
__internal__: {
219+
hasHeaders: false,
220+
},
221+
},
222+
isValid: true,
223+
});
224+
});
225+
});
226+
it('succeeds with headers', async () => {
227+
const testFormData = {
228+
actionTypeId: '.gen-ai',
229+
name: 'OpenAI',
230+
id: '123',
231+
config: {
232+
apiUrl: 'https://openaiurl.com',
233+
apiProvider: OpenAiProviderType.OpenAi,
234+
defaultModel: DEFAULT_OPENAI_MODEL,
235+
},
236+
secrets: {
237+
apiKey: 'thats-a-nice-looking-key',
238+
},
239+
isDeprecated: false,
240+
__internal__: {
241+
hasHeaders: false,
242+
},
243+
};
244+
const onSubmit = jest.fn();
245+
render(
246+
<ConnectorFormTestProvider connector={testFormData} onSubmit={onSubmit}>
247+
<ConnectorFields readOnly={false} isEdit={false} registerPreSubmitValidator={() => {}} />
248+
</ConnectorFormTestProvider>
249+
);
250+
const headersToggle = await screen.findByTestId('openAIViewHeadersSwitch');
251+
expect(headersToggle).toBeInTheDocument();
252+
await userEvent.click(headersToggle);
253+
254+
await userEvent.type(screen.getByTestId('openAIHeadersKeyInput'), 'hello');
255+
await userEvent.type(screen.getByTestId('openAIHeadersValueInput'), 'world');
256+
await userEvent.click(await screen.findByTestId('form-test-provide-submit'));
257+
await waitFor(() => {
258+
expect(onSubmit).toHaveBeenCalledWith({
259+
data: {
260+
actionTypeId: '.gen-ai',
261+
name: 'OpenAI',
262+
id: '123',
263+
isDeprecated: false,
264+
config: {
265+
apiUrl: 'https://openaiurl.com',
266+
apiProvider: OpenAiProviderType.OpenAi,
267+
defaultModel: DEFAULT_OPENAI_MODEL,
268+
headers: [{ key: 'hello', value: 'world' }],
269+
},
270+
secrets: {
271+
apiKey: 'thats-a-nice-looking-key',
272+
},
273+
__internal__: {
274+
hasHeaders: true,
275+
},
276+
},
277+
isValid: true,
278+
});
279+
});
280+
});
123281
});
124282

125283
describe('Dashboard link', () => {

0 commit comments

Comments
 (0)