Skip to content

Commit 678de14

Browse files
committed
feat: add VPC network mode to TUI wizard and warnings
Adds VPC network mode configuration to the TUI wizard for both Create (template) and BYO (bring your own code) paths. Users can select PUBLIC or VPC mode, and when VPC is selected, provide subnet IDs and security group IDs with validation. Also adds VPC warning banners to dev and invoke TUI screens when the selected agent uses VPC network mode, alerting users about potential network behavior differences.
1 parent c75f4cd commit 678de14

13 files changed

Lines changed: 360 additions & 23 deletions

File tree

src/cli/tui/hooks/useDevServer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ export function useDevServer(options: { workingDir: string; port: number; agentN
288288
stop,
289289
logFilePath: loggerRef.current?.getRelativeLogPath(),
290290
hasMemory: (project?.memories?.length ?? 0) > 0,
291+
hasVpc: project?.agents.find(a => a.name === config?.agentName)?.networkMode === 'VPC',
291292
modelProvider: project?.agents.find(a => a.name === config?.agentName)?.modelProvider,
292293
};
293294
}

src/cli/tui/screens/agent/AddAgentScreen.tsx

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { APP_DIR, ConfigIO } from '../../../../lib';
2-
import type { ModelProvider } from '../../../../schema';
2+
import type { ModelProvider, NetworkMode } from '../../../../schema';
33
import { AgentNameSchema, DEFAULT_MODEL_IDS } from '../../../../schema';
44
import { computeDefaultCredentialEnvVarName } from '../../../operations/identity/create-identity';
55
import {
@@ -16,7 +16,16 @@ import type { SelectableItem } from '../../components';
1616
import { HELP_TEXT } from '../../constants';
1717
import { useListNavigation, useProject } from '../../hooks';
1818
import { generateUniqueName } from '../../utils';
19-
import { BUILD_TYPE_OPTIONS, GenerateWizardUI, getWizardHelpText, useGenerateWizard } from '../generate';
19+
import {
20+
BUILD_TYPE_OPTIONS,
21+
GenerateWizardUI,
22+
NETWORK_MODE_OPTIONS,
23+
getWizardHelpText,
24+
parseCommaSeparatedIds,
25+
useGenerateWizard,
26+
validateSecurityGroupsInput,
27+
validateSubnetsInput,
28+
} from '../generate';
2029
import type { BuildType } from '../generate';
2130
import type { AddAgentConfig, AgentType } from './types';
2231
import {
@@ -52,10 +61,27 @@ interface AddAgentScreenProps {
5261
// Steps for the initial phase (before branching to create or byo)
5362
type InitialStep = 'name' | 'agentType';
5463
// Steps for BYO path only (no framework/language - user's code already has these baked in)
55-
type ByoStep = 'codeLocation' | 'buildType' | 'modelProvider' | 'apiKey' | 'confirm';
64+
type ByoStep =
65+
| 'codeLocation'
66+
| 'buildType'
67+
| 'modelProvider'
68+
| 'apiKey'
69+
| 'networkMode'
70+
| 'subnets'
71+
| 'securityGroups'
72+
| 'confirm';
5673

5774
const INITIAL_STEPS: InitialStep[] = ['name', 'agentType'];
58-
const BYO_STEPS: ByoStep[] = ['codeLocation', 'buildType', 'modelProvider', 'apiKey', 'confirm'];
75+
const BYO_STEPS: ByoStep[] = [
76+
'codeLocation',
77+
'buildType',
78+
'modelProvider',
79+
'apiKey',
80+
'networkMode',
81+
'subnets',
82+
'securityGroups',
83+
'confirm',
84+
];
5985

6086
export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAgentScreenProps) {
6187
// Phase 1: name + agentType selection
@@ -75,6 +101,9 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
75101
buildType: 'CodeZip' as BuildType,
76102
modelProvider: 'Bedrock' as ModelProvider,
77103
apiKey: undefined as string | undefined,
104+
networkMode: 'PUBLIC' as NetworkMode,
105+
subnets: undefined as string[] | undefined,
106+
securityGroups: undefined as string[] | undefined,
78107
});
79108

80109
const { project } = useProject();
@@ -156,6 +185,9 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
156185
apiKey: generateWizard.config.apiKey,
157186
pythonVersion: DEFAULT_PYTHON_VERSION,
158187
memory: generateWizard.config.memory,
188+
networkMode: generateWizard.config.networkMode ?? 'PUBLIC',
189+
subnets: generateWizard.config.subnets,
190+
securityGroups: generateWizard.config.securityGroups,
159191
};
160192
onComplete(config);
161193
}, [name, generateWizard.config, onComplete]);
@@ -174,13 +206,17 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
174206
// BYO Path
175207
// ─────────────────────────────────────────────────────────────────────────────
176208

177-
// BYO steps filtering (remove apiKey for Bedrock)
209+
// BYO steps filtering (remove apiKey for Bedrock, subnets/securityGroups when not VPC)
178210
const byoSteps = useMemo(() => {
211+
let steps = BYO_STEPS;
179212
if (byoConfig.modelProvider === 'Bedrock') {
180-
return BYO_STEPS.filter(s => s !== 'apiKey');
213+
steps = steps.filter(s => s !== 'apiKey');
214+
}
215+
if (byoConfig.networkMode !== 'VPC') {
216+
steps = steps.filter(s => s !== 'subnets' && s !== 'securityGroups');
181217
}
182-
return BYO_STEPS;
183-
}, [byoConfig.modelProvider]);
218+
return steps;
219+
}, [byoConfig.modelProvider, byoConfig.networkMode]);
184220

185221
const byoCurrentIndex = byoSteps.indexOf(byoStep);
186222

@@ -232,6 +268,9 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
232268
apiKey: byoConfig.apiKey,
233269
pythonVersion: DEFAULT_PYTHON_VERSION,
234270
memory: 'none',
271+
networkMode: byoConfig.networkMode,
272+
subnets: byoConfig.subnets,
273+
securityGroups: byoConfig.securityGroups,
235274
};
236275
onComplete(config);
237276
}, [name, byoConfig, onComplete]);
@@ -254,13 +293,40 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
254293
if (provider !== 'Bedrock') {
255294
setByoStep('apiKey');
256295
} else {
257-
setByoStep('confirm');
296+
setByoStep('networkMode');
258297
}
259298
},
260299
onExit: handleByoBack,
261300
isActive: isByoPath && byoStep === 'modelProvider',
262301
});
263302

303+
// BYO network mode options
304+
const networkModeItems: SelectableItem[] = useMemo(
305+
() =>
306+
NETWORK_MODE_OPTIONS.map(o => ({
307+
id: o.id,
308+
title: o.title,
309+
description: o.description,
310+
})),
311+
[]
312+
);
313+
314+
const networkModeNav = useListNavigation({
315+
items: networkModeItems,
316+
onSelect: item => {
317+
const mode = item.id as NetworkMode;
318+
if (mode === 'PUBLIC') {
319+
setByoConfig(c => ({ ...c, networkMode: mode, subnets: undefined, securityGroups: undefined }));
320+
setByoStep('confirm');
321+
} else {
322+
setByoConfig(c => ({ ...c, networkMode: mode }));
323+
setByoStep('subnets');
324+
}
325+
},
326+
onExit: handleByoBack,
327+
isActive: isByoPath && byoStep === 'networkMode',
328+
});
329+
264330
useListNavigation({
265331
items: [{ id: 'confirm', title: 'Confirm' }],
266332
onSelect: handleByoComplete,
@@ -281,7 +347,7 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
281347
return getWizardHelpText(generateWizard.step);
282348
}
283349
// BYO path
284-
if (byoStep === 'codeLocation' || byoStep === 'apiKey') {
350+
if (byoStep === 'codeLocation' || byoStep === 'apiKey' || byoStep === 'subnets' || byoStep === 'securityGroups') {
285351
return HELP_TEXT.TEXT_INPUT;
286352
}
287353
if (byoStep === 'confirm') {
@@ -413,10 +479,58 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
413479
envVarName={getProviderInfo(byoConfig.modelProvider).envVarName}
414480
onSubmit={apiKey => {
415481
setByoConfig(c => ({ ...c, apiKey }));
482+
setByoStep('networkMode');
483+
}}
484+
onSkip={() => setByoStep('networkMode')}
485+
onCancel={handleByoBack}
486+
/>
487+
)}
488+
489+
{byoStep === 'networkMode' && (
490+
<WizardSelect
491+
title="Select network mode"
492+
items={networkModeItems}
493+
selectedIndex={networkModeNav.selectedIndex}
494+
/>
495+
)}
496+
497+
{byoStep === 'subnets' && (
498+
<Box flexDirection="column">
499+
<Text color="yellow">
500+
Note: Your agent will run inside these VPC subnets. Ensure they have connectivity to required services
501+
(S3, ECR, Bedrock) and public internet if using public MCP servers or non-Bedrock model providers.
502+
</Text>
503+
<Box marginTop={1}>
504+
<TextInput
505+
prompt="Subnet IDs (comma-separated)"
506+
initialValue={byoConfig.subnets?.join(', ') ?? ''}
507+
onSubmit={value => {
508+
const result = validateSubnetsInput(value);
509+
if (result !== true) return false;
510+
setByoConfig(c => ({ ...c, subnets: parseCommaSeparatedIds(value) }));
511+
setByoStep('securityGroups');
512+
return true;
513+
}}
514+
onCancel={handleByoBack}
515+
customValidation={validateSubnetsInput}
516+
/>
517+
</Box>
518+
</Box>
519+
)}
520+
521+
{byoStep === 'securityGroups' && (
522+
<TextInput
523+
prompt="Security Group IDs (comma-separated)"
524+
initialValue={byoConfig.securityGroups?.join(', ') ?? ''}
525+
onSubmit={value => {
526+
const result = validateSecurityGroupsInput(value);
527+
if (result !== true) return false;
528+
setByoConfig(c => ({ ...c, securityGroups: parseCommaSeparatedIds(value) }));
416529
setByoStep('confirm');
530+
return true;
417531
}}
418-
onSkip={() => setByoStep('confirm')}
419532
onCancel={handleByoBack}
533+
customValidation={validateSecurityGroupsInput}
420534
/>
421535
)}
422536

@@ -450,6 +564,13 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
450564
},
451565
]
452566
: []),
567+
{ label: 'Network Mode', value: byoConfig.networkMode },
568+
...(byoConfig.networkMode === 'VPC' && byoConfig.subnets
569+
? [{ label: 'Subnets', value: byoConfig.subnets.join(', ') }]
570+
: []),
571+
...(byoConfig.networkMode === 'VPC' && byoConfig.securityGroups
572+
? [{ label: 'Security Groups', value: byoConfig.securityGroups.join(', ') }]
573+
: []),
453574
]}
454575
/>
455576
)}

src/cli/tui/screens/agent/types.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import type { BuildType, ModelProvider, PythonRuntime, SDKFramework, TargetLanguage } from '../../../../schema';
1+
import type {
2+
BuildType,
3+
ModelProvider,
4+
NetworkMode,
5+
PythonRuntime,
6+
SDKFramework,
7+
TargetLanguage,
8+
} from '../../../../schema';
29
import { DEFAULT_MODEL_IDS, getSupportedModelProviders } from '../../../../schema';
310
import type { MemoryOption } from '../generate/types';
411

@@ -35,6 +42,9 @@ export type AddAgentStep =
3542
| 'modelProvider'
3643
| 'apiKey'
3744
| 'memory'
45+
| 'networkMode'
46+
| 'subnets'
47+
| 'securityGroups'
3848
| 'confirm';
3949

4050
export interface AddAgentConfig {
@@ -54,6 +64,12 @@ export interface AddAgentConfig {
5464
pythonVersion: PythonRuntime;
5565
/** Memory option (create path only) */
5666
memory: MemoryOption;
67+
/** Network mode for the agent runtime */
68+
networkMode: NetworkMode;
69+
/** VPC subnet IDs (required when networkMode is VPC) */
70+
subnets?: string[];
71+
/** VPC security group IDs (required when networkMode is VPC) */
72+
securityGroups?: string[];
5773
}
5874

5975
export const ADD_AGENT_STEP_LABELS: Record<AddAgentStep, string> = {
@@ -66,6 +82,9 @@ export const ADD_AGENT_STEP_LABELS: Record<AddAgentStep, string> = {
6682
modelProvider: 'Model',
6783
apiKey: 'API Key',
6884
memory: 'Memory',
85+
networkMode: 'Network',
86+
subnets: 'Subnets',
87+
securityGroups: 'Sec Groups',
6988
confirm: 'Confirm',
7089
};
7190

@@ -102,6 +121,11 @@ export const MODEL_PROVIDER_OPTIONS = [
102121
{ id: 'Gemini', title: `Google Gemini (${DEFAULT_MODEL_IDS.Gemini})`, description: 'Gemini models via Google API' },
103122
] as const;
104123

124+
export const NETWORK_MODE_OPTIONS = [
125+
{ id: 'PUBLIC', title: 'Public', description: 'Agent runs with public internet access (default)' },
126+
{ id: 'VPC', title: 'VPC', description: 'Agent runs inside your VPC subnets' },
127+
] as const;
128+
105129
/**
106130
* Get model provider options filtered by SDK framework compatibility.
107131
*/

src/cli/tui/screens/agent/useAddAgent.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,22 @@ export type AddAgentOutcome = AddAgentCreateResult | AddAgentByoResult | AddAgen
5454
* Maps AddAgentConfig (from BYO wizard) to v2 AgentEnvSpec for schema persistence.
5555
*/
5656
export function mapByoConfigToAgent(config: AddAgentConfig): AgentEnvSpec {
57-
return {
57+
const agent: AgentEnvSpec = {
5858
type: 'AgentCoreRuntime',
5959
name: config.name,
6060
build: config.buildType,
6161
entrypoint: config.entrypoint as FilePath,
6262
codeLocation: config.codeLocation as DirectoryPath,
6363
runtimeVersion: config.pythonVersion,
64-
networkMode: 'PUBLIC',
64+
networkMode: config.networkMode ?? 'PUBLIC',
6565
};
66+
if (config.networkMode === 'VPC' && config.subnets && config.securityGroups) {
67+
agent.networkConfig = {
68+
subnets: config.subnets,
69+
securityGroups: config.securityGroups,
70+
};
71+
}
72+
return agent;
6673
}
6774

6875
/**
@@ -76,6 +83,9 @@ function mapAddAgentConfigToGenerateConfig(config: AddAgentConfig): GenerateConf
7683
modelProvider: config.modelProvider,
7784
memory: config.memory,
7885
language: config.language,
86+
networkMode: config.networkMode,
87+
subnets: config.subnets,
88+
securityGroups: config.securityGroups,
7989
};
8090
}
8191

src/cli/tui/screens/create/useCreateFlow.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ export function useCreateFlow(cwd: string): CreateFlowState {
274274
memory: addAgentConfig.memory,
275275
language: addAgentConfig.language,
276276
apiKey: addAgentConfig.apiKey,
277+
networkMode: addAgentConfig.networkMode,
278+
subnets: addAgentConfig.subnets,
279+
securityGroups: addAgentConfig.securityGroups,
277280
};
278281

279282
logger.logSubStep(`Framework: ${generateConfig.sdk}`);

src/cli/tui/screens/dev/DevScreen.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export function DevScreen(props: DevScreenProps) {
177177
stop,
178178
logFilePath,
179179
hasMemory,
180+
hasVpc,
180181
modelProvider,
181182
} = useDevServer({
182183
workingDir,
@@ -444,6 +445,12 @@ export function DevScreen(props: DevScreenProps) {
444445
AgentCore memory is not available when running locally. To test memory, deploy and use invoke.
445446
</Text>
446447
)}
448+
{hasVpc && (
449+
<Text color="yellow">
450+
This agent uses VPC network mode. Local dev server runs outside your VPC. Network behavior may differ from
451+
deployed environment.
452+
</Text>
453+
)}
447454
</Box>
448455
);
449456

0 commit comments

Comments
 (0)