@@ -48,6 +48,7 @@ import { ServiceAccountSection } from 'components/ui/service-account/service-acc
4848import { Edit , Plus , Save , Settings , ShieldCheck , Trash2 } from 'lucide-react' ;
4949import { Scope } from 'protogen/redpanda/api/dataplane/v1/secret_pb' ;
5050import {
51+ AIAgent_GatewayConfigSchema ,
5152 AIAgent_MCPServerSchema ,
5253 type AIAgent_Provider ,
5354 AIAgent_Provider_AnthropicSchema ,
@@ -59,8 +60,10 @@ import {
5960 AIAgentUpdateSchema ,
6061 UpdateAIAgentRequestSchema ,
6162} from 'protogen/redpanda/api/dataplane/v1alpha3/ai_agent_pb' ;
63+ import { type AIGateway , AIGateway_State } from 'protogen/redpanda/api/dataplane/v1alpha3/ai_gateway_pb' ;
6264import { useCallback , useMemo , useState } from 'react' ;
6365import { useGetAIAgentQuery , useUpdateAIAgentMutation } from 'react-query/api/ai-agent' ;
66+ import { useListAIGatewaysQuery } from 'react-query/api/ai-gateway' ;
6467import { type MCPServer , useListMCPServersQuery } from 'react-query/api/remote-mcp' ;
6568import { useListSecretsQuery } from 'react-query/api/secret' ;
6669import { toast } from 'sonner' ;
@@ -88,6 +91,7 @@ type LocalAIAgent = {
8891 systemPrompt : string ;
8992 selectedMcpServers : string [ ] ;
9093 } > ;
94+ gatewayId ?: string ;
9195} ;
9296
9397/**
@@ -272,6 +276,32 @@ export const AIAgentConfigurationTab = () => {
272276 const { data : mcpServersData } = useListMCPServersQuery ( ) ;
273277 const { data : secretsData } = useListSecretsQuery ( ) ;
274278
279+ // Gateway detection
280+ const { data : gatewaysData , isLoading : isLoadingGateways } = useListAIGatewaysQuery (
281+ { pageSize : - 1 } ,
282+ { enabled : true }
283+ ) ;
284+
285+ const hasGatewayDeployed = useMemo ( ( ) => {
286+ if ( isLoadingGateways ) {
287+ return false ;
288+ }
289+ return Boolean ( gatewaysData ?. aiGateways && gatewaysData . aiGateways . length > 0 ) ;
290+ } , [ gatewaysData , isLoadingGateways ] ) ;
291+
292+ const availableGateways = useMemo ( ( ) => {
293+ if ( ! gatewaysData ?. aiGateways ) {
294+ return [ ] ;
295+ }
296+ return gatewaysData . aiGateways
297+ . filter ( ( gw : AIGateway ) => gw . state === AIGateway_State . RUNNING )
298+ . map ( ( gw : AIGateway ) => ( {
299+ id : gw . id ,
300+ displayName : gw . displayName ,
301+ description : gw . description ,
302+ } ) ) ;
303+ } , [ gatewaysData ] ) ;
304+
275305 const [ isEditing , setIsEditing ] = useState ( false ) ;
276306 const [ editedAgentData , setEditedAgentData ] = useState < LocalAIAgent | null > ( null ) ;
277307 const [ expandedSubagent , setExpandedSubagent ] = useState < string | undefined > ( undefined ) ;
@@ -341,6 +371,7 @@ export const AIAgentConfigurationTab = () => {
341371 systemPrompt : subagent . systemPrompt ,
342372 selectedMcpServers : Object . values ( subagent . mcpServers || { } ) . map ( ( server ) => server . id ) ,
343373 } ) ) ,
374+ gatewayId : aiAgentData . aiAgent . gateway ?. virtualGatewayId ,
344375 } ;
345376 }
346377
@@ -601,6 +632,13 @@ export const AIAgentConfigurationTab = () => {
601632 const apiKeyRef = `\${secrets.${ currentData . apiKeySecret } }` ;
602633 const updatedProvider = createUpdatedProvider ( currentData . provider . provider . case , apiKeyRef , currentData . baseUrl ) ;
603634
635+ const gatewayConfig =
636+ currentData . gatewayId && currentData . gatewayId . trim ( ) !== ''
637+ ? create ( AIAgent_GatewayConfigSchema , {
638+ virtualGatewayId : currentData . gatewayId ,
639+ } )
640+ : undefined ;
641+
604642 await updateAIAgent (
605643 create ( UpdateAIAgentRequestSchema , {
606644 id,
@@ -619,6 +657,7 @@ export const AIAgentConfigurationTab = () => {
619657 mcpServers : mcpServersMap ,
620658 subagents : subagentsMap ,
621659 tags : tagsMap ,
660+ gateway : gatewayConfig ,
622661 } ) ,
623662 updateMask : create ( FieldMaskSchema , {
624663 paths : [
@@ -630,6 +669,7 @@ export const AIAgentConfigurationTab = () => {
630669 'system_prompt' ,
631670 'service_account' ,
632671 'resources' ,
672+ 'gateway' ,
633673 'mcp_servers' ,
634674 'subagents' ,
635675 'tags' ,
@@ -1007,10 +1047,35 @@ export const AIAgentConfigurationTab = () => {
10071047 < CardContent className = "px-4 pb-4" >
10081048 { isEditing ? (
10091049 < div className = "space-y-4" >
1050+ { /* Gateway Selection - only show if gateways deployed */ }
1051+ { hasGatewayDeployed && availableGateways . length > 0 && (
1052+ < div className = "space-y-2" >
1053+ < Label > AI Gateway</ Label >
1054+ < Text className = "text-muted-foreground text-xs" > Route requests through an AI Gateway</ Text >
1055+ < Select
1056+ onValueChange = { ( value ) => updateField ( { gatewayId : value || undefined } ) }
1057+ value = { displayData . gatewayId || '' }
1058+ >
1059+ < SelectTrigger >
1060+ < SelectValue placeholder = "No gateway" />
1061+ </ SelectTrigger >
1062+ < SelectContent >
1063+ < SelectItem value = "" > No gateway</ SelectItem >
1064+ { availableGateways . map ( ( gw : { id : string ; displayName : string ; description : string } ) => (
1065+ < SelectItem key = { gw . id } value = { gw . id } >
1066+ { gw . displayName }
1067+ </ SelectItem >
1068+ ) ) }
1069+ </ SelectContent >
1070+ </ Select >
1071+ </ div >
1072+ ) }
1073+
10101074 { /* Provider - now editable */ }
10111075 < div className = "space-y-2" >
10121076 < Label htmlFor = "provider" > Provider</ Label >
10131077 < Select
1078+ disabled = { ! ! displayData . gatewayId }
10141079 onValueChange = { ( value : 'openai' | 'anthropic' | 'google' | 'openaiCompatible' ) => {
10151080 const newProviderData = MODEL_OPTIONS_BY_PROVIDER [ value ] ;
10161081 const firstModel =
@@ -1071,12 +1136,17 @@ export const AIAgentConfigurationTab = () => {
10711136 < Label htmlFor = "model" > Model</ Label >
10721137 { displayData . provider ?. provider . case === 'openaiCompatible' ? (
10731138 < Input
1139+ disabled = { ! ! displayData . gatewayId }
10741140 onChange = { ( e ) => updateField ( { model : e . target . value } ) }
10751141 placeholder = "Enter model name (e.g., llama-3.1-70b)"
10761142 value = { displayData . model }
10771143 />
10781144 ) : (
1079- < Select onValueChange = { ( value ) => updateField ( { model : value } ) } value = { displayData . model } >
1145+ < Select
1146+ disabled = { ! ! displayData . gatewayId }
1147+ onValueChange = { ( value ) => updateField ( { model : value } ) }
1148+ value = { displayData . model }
1149+ >
10801150 < SelectTrigger >
10811151 < SelectValue >
10821152 { Boolean ( displayData . model ) && detectProvider ( displayData . model ) ? (
@@ -1137,23 +1207,25 @@ export const AIAgentConfigurationTab = () => {
11371207 ) }
11381208 </ div >
11391209
1140- { /* API Token */ }
1141- < div className = "space-y-2" >
1142- < Label htmlFor = "apiKeySecret" > API Token</ Label >
1143- < div className = "[&>div]:flex-col [&>div]:items-stretch [&>div]:gap-2" >
1144- < SecretSelector
1145- availableSecrets = { availableSecrets }
1146- customText = { AI_AGENT_SECRET_TEXT }
1147- onChange = { ( value ) => updateField ( { apiKeySecret : value } ) }
1148- placeholder = "Select from secrets store or create new"
1149- scopes = { [ Scope . MCP_SERVER , Scope . AI_AGENT ] }
1150- value = { displayData . apiKeySecret }
1151- />
1210+ { /* API Token - HIDE if using gateway */ }
1211+ { ! displayData . gatewayId && (
1212+ < div className = "space-y-2" >
1213+ < Label htmlFor = "apiKeySecret" > API Token</ Label >
1214+ < div className = "[&>div]:flex-col [&>div]:items-stretch [&>div]:gap-2" >
1215+ < SecretSelector
1216+ availableSecrets = { availableSecrets }
1217+ customText = { AI_AGENT_SECRET_TEXT }
1218+ onChange = { ( value ) => updateField ( { apiKeySecret : value } ) }
1219+ placeholder = "Select from secrets store or create new"
1220+ scopes = { [ Scope . MCP_SERVER , Scope . AI_AGENT ] }
1221+ value = { displayData . apiKeySecret }
1222+ />
1223+ </ div >
11521224 </ div >
1153- </ div >
1225+ ) }
11541226
1155- { /* Base URL - only show for openaiCompatible */ }
1156- { displayData . provider ?. provider . case === 'openaiCompatible' && (
1227+ { /* Base URL - only show for openaiCompatible and when not using gateway */ }
1228+ { ! displayData . gatewayId && displayData . provider ?. provider . case === 'openaiCompatible' && (
11571229 < div className = "space-y-2" >
11581230 < Label htmlFor = "baseUrl" > Base URL (required)</ Label >
11591231 < Input
@@ -1181,6 +1253,21 @@ export const AIAgentConfigurationTab = () => {
11811253 </ div >
11821254 ) : (
11831255 < div className = "space-y-4" >
1256+ { /* Gateway - only show if gateways deployed */ }
1257+ { hasGatewayDeployed && availableGateways . length > 0 && (
1258+ < div className = "space-y-2" >
1259+ < Label > AI Gateway</ Label >
1260+ < div className = "flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2" >
1261+ < Text variant = "default" >
1262+ { displayData . gatewayId
1263+ ? availableGateways . find ( ( gw : { id : string ; displayName : string ; description : string } ) => gw . id === displayData . gatewayId ) ?. displayName ||
1264+ displayData . gatewayId
1265+ : 'None' }
1266+ </ Text >
1267+ </ div >
1268+ </ div >
1269+ ) }
1270+
11841271 < div className = "space-y-2" >
11851272 < Label > Provider</ Label >
11861273 < div className = "flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2" >
@@ -1198,20 +1285,25 @@ export const AIAgentConfigurationTab = () => {
11981285 < AIAgentModel model = { displayData . model } />
11991286 </ div >
12001287 </ div >
1201- < div className = "space-y-2" >
1202- < Label > API Token</ Label >
1203- < div className = "flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2" >
1204- < Text variant = "default" > { displayData . apiKeySecret || 'No secret configured' } </ Text >
1205- </ div >
1206- </ div >
1207- { agent . provider ?. provider . case === 'openaiCompatible' && displayData . baseUrl && (
1288+ { /* API Token - HIDE if using gateway */ }
1289+ { ! displayData . gatewayId && (
12081290 < div className = "space-y-2" >
1209- < Label > Base URL </ Label >
1291+ < Label > API Token </ Label >
12101292 < div className = "flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2" >
1211- < Text variant = "default" > { displayData . baseUrl } </ Text >
1293+ < Text variant = "default" > { displayData . apiKeySecret || 'No secret configured' } </ Text >
12121294 </ div >
12131295 </ div >
12141296 ) }
1297+ { ! displayData . gatewayId &&
1298+ agent . provider ?. provider . case === 'openaiCompatible' &&
1299+ displayData . baseUrl && (
1300+ < div className = "space-y-2" >
1301+ < Label > Base URL</ Label >
1302+ < div className = "flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2" >
1303+ < Text variant = "default" > { displayData . baseUrl } </ Text >
1304+ </ div >
1305+ </ div >
1306+ ) }
12151307 < div className = "space-y-2" >
12161308 < Label > Max Iterations</ Label >
12171309 < div className = "flex h-10 items-center rounded-md border border-gray-200 bg-gray-50 px-3 py-2" >
0 commit comments