Skip to content

Commit 6841e46

Browse files
authored
Merge pull request #2175 from redpanda-data/av/ai-gateway-integration-ui
feat(frontend): Add AI Gateway integration for dynamic provider and model selection
2 parents 65486ee + cebef26 commit 6841e46

78 files changed

Lines changed: 23149 additions & 414 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

buf.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ deps:
1010
- name: buf.build/grpc-ecosystem/grpc-gateway
1111
commit: a48fcebcf8f140dd9d09359b9bb185a4
1212
digest: b5:330af8a71b579ab96c4f3ee26929d1a68a5a9e986c7cfe0a898591fc514216bb6e723dc04c74d90fdee3f3f14f9100a54b4f079eb273e6e7213f0d5baca36ff8
13-
- name: buf.build/opentelemetry/opentelemetry
14-
commit: 648a3e2f02e14fe187656ea4ac3befa1
15-
digest: b5:a0514be587ab2e8598f7102dfdaba5e985138b8c041c707e470a9c8b877410ada6e60cf2359352302f08c0504de685379f7f7a085d4699d69a2e6ed7035e78d9
13+
- name: buf.build/redpandadata/ai-gateway
14+
commit: 702ee5c67c994d9995e39e74bb6553f6
15+
digest: b5:9717dd679ced8e42eab232efcc89c4ebcf9334b5aa6664b502268d5c7d25766e2c23b201822383a0a7c943d663859c7054c0656937fce7b612f7b578a59e3eb2
1616
- name: buf.build/redpandadata/common
1717
commit: 601698cfe71d43b1b36fd434a7f765f1
1818
digest: b5:d566ba6746a874a5709970e7f8569584008447d655c556676aabd3430111bbbc0a303dde11d99a1955ee27ac4748bcf7c972503a66db4ef850eb55f239f851ac

buf.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ deps:
1111
- buf.build/redpandadata/common
1212
- buf.build/redpandadata/core:05aa34b3829a4d5a801d9487623a7c76
1313
- buf.build/redpandadata/otel
14+
- buf.build/redpandadata/ai-gateway
1415
lint:
1516
use:
1617
- STANDARD

frontend/bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/rsbuild.config.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,31 @@ export default defineConfig({
5353
origin: ['http://localhost:3000', 'http://localhost:9090'],
5454
credentials: true,
5555
},
56-
proxy: {
57-
context: ['/api', '/redpanda.api', '/auth', '/logout'],
58-
target: process.env.PROXY_TARGET || 'http://localhost:9090',
59-
changeOrigin: !!process.env.PROXY_TARGET,
60-
secure: process.env.PROXY_TARGET ? false : undefined,
61-
},
56+
proxy: [
57+
// AI Gateway API - proxy to separate AI Gateway service
58+
// Matches: /.redpanda/api/redpanda.api.aigateway.v1.*
59+
// Proto package is: redpanda.api.aigateway.v1 (includes .api)
60+
// AI Gateway now expects the full path with .api
61+
...(process.env.AI_GATEWAY_URL
62+
? [
63+
{
64+
context: ['/.redpanda/api/redpanda.api.aigateway.v1'],
65+
target: process.env.AI_GATEWAY_URL,
66+
changeOrigin: true,
67+
secure: false,
68+
logLevel: 'debug',
69+
// No pathRewrite - AI Gateway expects full path with .api
70+
},
71+
]
72+
: []),
73+
// All other APIs - proxy to Console backend
74+
{
75+
context: ['/api', '/redpanda.api', '/auth', '/logout'],
76+
target: process.env.PROXY_TARGET || 'http://localhost:9090',
77+
changeOrigin: !!process.env.PROXY_TARGET,
78+
secure: process.env.PROXY_TARGET ? false : undefined,
79+
},
80+
],
6281
},
6382
source: {
6483
define: {

frontend/src/components/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const FEATURE_FLAGS = {
1818
enableMcpServiceAccount: false,
1919
enablePipelineServiceAccount: false,
2020
enableTranscriptsInConsole: false,
21+
enableApiKeyConfigurationAgent: false,
2122
shadowlinkCloudUi: false,
2223
enableNewTheme: false,
2324
};

frontend/src/components/pages/agents/create/ai-agent-create-page.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
type ServiceAccountSelectorRef,
3333
} from 'components/ui/service-account/service-account-selector';
3434
import { TagsFieldList } from 'components/ui/tag/tags-field-list';
35+
import { isFeatureFlagEnabled } from 'config';
3536
import { Loader2 } from 'lucide-react';
3637
import { Scope } from 'protogen/redpanda/api/dataplane/v1/secret_pb';
3738
import {
@@ -51,6 +52,7 @@ import {
5152
import { useEffect, useMemo, useRef, useState } from 'react';
5253
import { Controller, useFieldArray, useForm } from 'react-hook-form';
5354
import { useCreateAIAgentMutation } from 'react-query/api/ai-agent';
55+
import { useListGatewaysQuery } from 'react-query/api/ai-gateway';
5456
import { useListMCPServersQuery } from 'react-query/api/remote-mcp';
5557
import { useCreateSecretMutation, useListSecretsQuery } from 'react-query/api/secret';
5658
import { toast } from 'sonner';
@@ -72,6 +74,38 @@ export const AIAgentCreatePage = () => {
7274
skipInvalidation: true,
7375
});
7476

77+
// Feature flag: when true, use legacy API key mode (hardcoded providers)
78+
const isLegacyApiKeyMode = isFeatureFlagEnabled('enableApiKeyConfigurationAgent');
79+
80+
// Gateway detection and list query (using v1 API from ai-gateway module)
81+
// Only fetch when NOT in legacy mode
82+
const { data: gatewaysData, isLoading: isLoadingGateways } = useListGatewaysQuery(
83+
{},
84+
{ enabled: !isLegacyApiKeyMode }
85+
);
86+
87+
const hasGatewayDeployed = useMemo(() => {
88+
if (isLegacyApiKeyMode || isLoadingGateways) {
89+
return false;
90+
}
91+
return Boolean(gatewaysData?.gateways && gatewaysData.gateways.length > 0);
92+
}, [isLegacyApiKeyMode, gatewaysData, isLoadingGateways]);
93+
94+
const availableGateways = useMemo(() => {
95+
if (isLegacyApiKeyMode || !gatewaysData?.gateways) {
96+
return [];
97+
}
98+
return gatewaysData.gateways.map((gw) => {
99+
// Extract gateway ID from name (format: "gateways/{gateway_id}")
100+
const gatewayId = gw.name.split('/').pop() || gw.name;
101+
return {
102+
id: gatewayId,
103+
displayName: gw.displayName,
104+
description: gw.description,
105+
};
106+
});
107+
}, [isLegacyApiKeyMode, gatewaysData]);
108+
75109
// Ref to ServiceAccountSelector to call createServiceAccount
76110
const serviceAccountSelectorRef = useRef<ServiceAccountSelectorRef>(null);
77111

@@ -107,6 +141,13 @@ export const AIAgentCreatePage = () => {
107141
}
108142
}, [displayName, form]);
109143

144+
// Auto-select first gateway when gateways are available (only if not in legacy mode)
145+
useEffect(() => {
146+
if (!isLegacyApiKeyMode && availableGateways.length > 0 && !form.getValues('gatewayId')) {
147+
form.setValue('gatewayId', availableGateways[0].id);
148+
}
149+
}, [isLegacyApiKeyMode, availableGateways, form]);
150+
110151
const {
111152
fields: tagFields,
112153
append: appendTag,
@@ -294,7 +335,9 @@ export const AIAgentCreatePage = () => {
294335
});
295336

296337
// Build provider configuration based on selected provider
297-
const apiKeyRef = `\${secrets.${values.apiKeySecret}}`;
338+
// When using gateway: api_key can be empty (proto has ignore = IGNORE_IF_ZERO_VALUE)
339+
// When not using gateway: api_key must reference a secret
340+
const apiKeyRef = values.apiKeySecret ? `\${secrets.${values.apiKeySecret}}` : '';
298341
let providerConfig: AIAgent_Provider;
299342

300343
switch (values.provider) {
@@ -464,7 +507,7 @@ export const AIAgentCreatePage = () => {
464507
</CardHeader>
465508
<CardContent>
466509
<LLMConfigSection
467-
availableGateways={[]}
510+
availableGateways={availableGateways}
468511
availableSecrets={availableSecrets}
469512
fieldNames={{
470513
provider: 'provider',
@@ -475,7 +518,8 @@ export const AIAgentCreatePage = () => {
475518
gatewayId: 'gatewayId',
476519
}}
477520
form={form}
478-
hasGatewayDeployed={false}
521+
hasGatewayDeployed={hasGatewayDeployed}
522+
isLoadingGateways={isLoadingGateways}
479523
mode="create"
480524
scopes={[Scope.MCP_SERVER, Scope.AI_AGENT]}
481525
showBaseUrl={form.watch('provider') === 'openaiCompatible'}

frontend/src/components/pages/agents/create/schemas.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ export const FormSchema = z
4949
{ message: 'Tags must have unique keys' }
5050
),
5151
triggerType: z.enum(['http', 'slack', 'kafka']).default('http'),
52+
gatewayId: z
53+
.string()
54+
.refine(
55+
(val) => !val || (val.length === 20 && /^[a-z0-9]+$/.test(val)),
56+
'Gateway ID must be exactly 20 lowercase alphanumeric characters'
57+
)
58+
.optional()
59+
.or(z.literal('')),
5260
provider: z.enum(['openai', 'anthropic', 'google', 'openaiCompatible']).default('openai'),
5361
apiKeySecret: z.string(),
5462
model: z.string().min(1, 'Model is required'),
@@ -77,15 +85,24 @@ export const FormSchema = z
7785
},
7886
{ message: 'Subagent names must be unique' }
7987
),
80-
gatewayId: z
81-
.string()
82-
.length(20, 'Gateway ID must be exactly 20 characters')
83-
.regex(/^[a-z0-9]+$/, 'Gateway ID must contain only lowercase letters and numbers')
84-
.optional()
85-
.or(z.literal('')),
8688
})
8789
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Complex validation logic with multiple conditional checks
8890
.superRefine((data, ctx) => {
91+
// Note: Gateway validation happens in the UI layer based on availability
92+
// If gateways are available, gateway is required (enforced by UI)
93+
// If gateways are NOT available, API key is required
94+
95+
const hasGateway = data.gatewayId && data.gatewayId.trim() !== '';
96+
97+
if (!hasGateway && (!data.apiKeySecret || data.apiKeySecret.trim() === '')) {
98+
// No gateway selected: API Key is required
99+
ctx.addIssue({
100+
code: z.ZodIssueCode.custom,
101+
message: 'API Token is required',
102+
path: ['apiKeySecret'],
103+
});
104+
}
105+
89106
if (data.provider === 'openaiCompatible') {
90107
if (!data.baseUrl || data.baseUrl.trim() === '') {
91108
ctx.addIssue({
@@ -131,6 +148,7 @@ export const initialValues: FormValues = {
131148
description: '',
132149
tags: [],
133150
triggerType: 'http',
151+
gatewayId: '',
134152
provider: 'openai',
135153
apiKeySecret: '',
136154
model: '',
@@ -142,5 +160,4 @@ export const initialValues: FormValues = {
142160
systemPrompt: '',
143161
serviceAccountName: '',
144162
subagents: [],
145-
gatewayId: '',
146163
};

0 commit comments

Comments
 (0)