Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cli/tui/hooks/useDevServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export function useDevServer(options: { workingDir: string; port: number; agentN
stop,
logFilePath: loggerRef.current?.getRelativeLogPath(),
hasMemory: (project?.memories?.length ?? 0) > 0,
hasVpc: project?.agents.find(a => a.name === config?.agentName)?.networkMode === 'VPC',
modelProvider: project?.agents.find(a => a.name === config?.agentName)?.modelProvider,
};
}
143 changes: 132 additions & 11 deletions src/cli/tui/screens/agent/AddAgentScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APP_DIR, ConfigIO } from '../../../../lib';
import type { ModelProvider } from '../../../../schema';
import type { ModelProvider, NetworkMode } from '../../../../schema';
import { AgentNameSchema, DEFAULT_MODEL_IDS } from '../../../../schema';
import { computeDefaultCredentialEnvVarName } from '../../../operations/identity/create-identity';
import {
Expand All @@ -16,7 +16,16 @@ import type { SelectableItem } from '../../components';
import { HELP_TEXT } from '../../constants';
import { useListNavigation, useProject } from '../../hooks';
import { generateUniqueName } from '../../utils';
import { BUILD_TYPE_OPTIONS, GenerateWizardUI, getWizardHelpText, useGenerateWizard } from '../generate';
import {
BUILD_TYPE_OPTIONS,
GenerateWizardUI,
NETWORK_MODE_OPTIONS,
getWizardHelpText,
parseCommaSeparatedIds,
useGenerateWizard,
validateSecurityGroupsInput,
validateSubnetsInput,
} from '../generate';
import type { BuildType } from '../generate';
import type { AddAgentConfig, AgentType } from './types';
import {
Expand Down Expand Up @@ -52,10 +61,27 @@ interface AddAgentScreenProps {
// Steps for the initial phase (before branching to create or byo)
type InitialStep = 'name' | 'agentType';
// Steps for BYO path only (no framework/language - user's code already has these baked in)
type ByoStep = 'codeLocation' | 'buildType' | 'modelProvider' | 'apiKey' | 'confirm';
type ByoStep =
| 'codeLocation'
| 'buildType'
| 'modelProvider'
| 'apiKey'
| 'networkMode'
| 'subnets'
| 'securityGroups'
| 'confirm';

const INITIAL_STEPS: InitialStep[] = ['name', 'agentType'];
const BYO_STEPS: ByoStep[] = ['codeLocation', 'buildType', 'modelProvider', 'apiKey', 'confirm'];
const BYO_STEPS: ByoStep[] = [
'codeLocation',
'buildType',
'modelProvider',
'apiKey',
'networkMode',
'subnets',
'securityGroups',
'confirm',
];

export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAgentScreenProps) {
// Phase 1: name + agentType selection
Expand All @@ -75,6 +101,9 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
buildType: 'CodeZip' as BuildType,
modelProvider: 'Bedrock' as ModelProvider,
apiKey: undefined as string | undefined,
networkMode: 'PUBLIC' as NetworkMode,
subnets: undefined as string[] | undefined,
securityGroups: undefined as string[] | undefined,
});

const { project } = useProject();
Expand Down Expand Up @@ -156,6 +185,9 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
apiKey: generateWizard.config.apiKey,
pythonVersion: DEFAULT_PYTHON_VERSION,
memory: generateWizard.config.memory,
networkMode: generateWizard.config.networkMode ?? 'PUBLIC',
subnets: generateWizard.config.subnets,
securityGroups: generateWizard.config.securityGroups,
};
onComplete(config);
}, [name, generateWizard.config, onComplete]);
Expand All @@ -174,13 +206,17 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
// BYO Path
// ─────────────────────────────────────────────────────────────────────────────

// BYO steps filtering (remove apiKey for Bedrock)
// BYO steps filtering (remove apiKey for Bedrock, subnets/securityGroups when not VPC)
const byoSteps = useMemo(() => {
let steps = BYO_STEPS;
if (byoConfig.modelProvider === 'Bedrock') {
return BYO_STEPS.filter(s => s !== 'apiKey');
steps = steps.filter(s => s !== 'apiKey');
}
if (byoConfig.networkMode !== 'VPC') {
steps = steps.filter(s => s !== 'subnets' && s !== 'securityGroups');
}
return BYO_STEPS;
}, [byoConfig.modelProvider]);
return steps;
}, [byoConfig.modelProvider, byoConfig.networkMode]);

const byoCurrentIndex = byoSteps.indexOf(byoStep);

Expand Down Expand Up @@ -232,6 +268,9 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
apiKey: byoConfig.apiKey,
pythonVersion: DEFAULT_PYTHON_VERSION,
memory: 'none',
networkMode: byoConfig.networkMode,
subnets: byoConfig.subnets,
securityGroups: byoConfig.securityGroups,
};
onComplete(config);
}, [name, byoConfig, onComplete]);
Expand All @@ -254,13 +293,40 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
if (provider !== 'Bedrock') {
setByoStep('apiKey');
} else {
setByoStep('confirm');
setByoStep('networkMode');
}
},
onExit: handleByoBack,
isActive: isByoPath && byoStep === 'modelProvider',
});

// BYO network mode options
const networkModeItems: SelectableItem[] = useMemo(
() =>
NETWORK_MODE_OPTIONS.map(o => ({
id: o.id,
title: o.title,
description: o.description,
})),
[]
);

const networkModeNav = useListNavigation({
items: networkModeItems,
onSelect: item => {
const mode = item.id as NetworkMode;
if (mode === 'PUBLIC') {
setByoConfig(c => ({ ...c, networkMode: mode, subnets: undefined, securityGroups: undefined }));
setByoStep('confirm');
} else {
setByoConfig(c => ({ ...c, networkMode: mode }));
setByoStep('subnets');
}
},
onExit: handleByoBack,
isActive: isByoPath && byoStep === 'networkMode',
});

useListNavigation({
items: [{ id: 'confirm', title: 'Confirm' }],
onSelect: handleByoComplete,
Expand All @@ -281,7 +347,7 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
return getWizardHelpText(generateWizard.step);
}
// BYO path
if (byoStep === 'codeLocation' || byoStep === 'apiKey') {
if (byoStep === 'codeLocation' || byoStep === 'apiKey' || byoStep === 'subnets' || byoStep === 'securityGroups') {
return HELP_TEXT.TEXT_INPUT;
}
if (byoStep === 'confirm') {
Expand Down Expand Up @@ -413,10 +479,58 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
envVarName={getProviderInfo(byoConfig.modelProvider).envVarName}
onSubmit={apiKey => {
setByoConfig(c => ({ ...c, apiKey }));
setByoStep('networkMode');
}}
onSkip={() => setByoStep('networkMode')}
onCancel={handleByoBack}
/>
)}

{byoStep === 'networkMode' && (
<WizardSelect
title="Select network mode"
items={networkModeItems}
selectedIndex={networkModeNav.selectedIndex}
/>
)}

{byoStep === 'subnets' && (
<Box flexDirection="column">
<Text color="yellow">
Note: Your agent will run inside these VPC subnets. Ensure they have connectivity to required services
(S3, ECR, Bedrock) and public internet if using public MCP servers or non-Bedrock model providers.
</Text>
<Box marginTop={1}>
<TextInput
prompt="Subnet IDs (comma-separated)"
initialValue={byoConfig.subnets?.join(', ') ?? ''}
onSubmit={value => {
const result = validateSubnetsInput(value);
if (result !== true) return false;
setByoConfig(c => ({ ...c, subnets: parseCommaSeparatedIds(value) }));
setByoStep('securityGroups');
return true;
}}
onCancel={handleByoBack}
customValidation={validateSubnetsInput}
/>
</Box>
</Box>
)}

{byoStep === 'securityGroups' && (
<TextInput
prompt="Security Group IDs (comma-separated)"
initialValue={byoConfig.securityGroups?.join(', ') ?? ''}
onSubmit={value => {
const result = validateSecurityGroupsInput(value);
if (result !== true) return false;
setByoConfig(c => ({ ...c, securityGroups: parseCommaSeparatedIds(value) }));
setByoStep('confirm');
return true;
}}
onSkip={() => setByoStep('confirm')}
onCancel={handleByoBack}
customValidation={validateSecurityGroupsInput}
/>
)}

Expand Down Expand Up @@ -450,6 +564,13 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
},
]
: []),
{ label: 'Network Mode', value: byoConfig.networkMode },
...(byoConfig.networkMode === 'VPC' && byoConfig.subnets
? [{ label: 'Subnets', value: byoConfig.subnets.join(', ') }]
: []),
...(byoConfig.networkMode === 'VPC' && byoConfig.securityGroups
? [{ label: 'Security Groups', value: byoConfig.securityGroups.join(', ') }]
: []),
]}
/>
)}
Expand Down
26 changes: 25 additions & 1 deletion src/cli/tui/screens/agent/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { BuildType, ModelProvider, PythonRuntime, SDKFramework, TargetLanguage } from '../../../../schema';
import type {
BuildType,
ModelProvider,
NetworkMode,
PythonRuntime,
SDKFramework,
TargetLanguage,
} from '../../../../schema';
import { DEFAULT_MODEL_IDS, getSupportedModelProviders } from '../../../../schema';
import type { MemoryOption } from '../generate/types';

Expand Down Expand Up @@ -35,6 +42,9 @@ export type AddAgentStep =
| 'modelProvider'
| 'apiKey'
| 'memory'
| 'networkMode'
| 'subnets'
| 'securityGroups'
| 'confirm';

export interface AddAgentConfig {
Expand All @@ -54,6 +64,12 @@ export interface AddAgentConfig {
pythonVersion: PythonRuntime;
/** Memory option (create path only) */
memory: MemoryOption;
/** Network mode for the agent runtime */
networkMode: NetworkMode;
/** VPC subnet IDs (required when networkMode is VPC) */
subnets?: string[];
/** VPC security group IDs (required when networkMode is VPC) */
securityGroups?: string[];
}

export const ADD_AGENT_STEP_LABELS: Record<AddAgentStep, string> = {
Expand All @@ -66,6 +82,9 @@ export const ADD_AGENT_STEP_LABELS: Record<AddAgentStep, string> = {
modelProvider: 'Model',
apiKey: 'API Key',
memory: 'Memory',
networkMode: 'Network',
subnets: 'Subnets',
securityGroups: 'Sec Groups',
confirm: 'Confirm',
};

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

export const NETWORK_MODE_OPTIONS = [
{ id: 'PUBLIC', title: 'Public', description: 'Agent runs with public internet access (default)' },
{ id: 'VPC', title: 'VPC', description: 'Agent runs inside your VPC subnets' },
] as const;

/**
* Get model provider options filtered by SDK framework compatibility.
*/
Expand Down
14 changes: 12 additions & 2 deletions src/cli/tui/screens/agent/useAddAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,22 @@ export type AddAgentOutcome = AddAgentCreateResult | AddAgentByoResult | AddAgen
* Maps AddAgentConfig (from BYO wizard) to v2 AgentEnvSpec for schema persistence.
*/
export function mapByoConfigToAgent(config: AddAgentConfig): AgentEnvSpec {
return {
const agent: AgentEnvSpec = {
type: 'AgentCoreRuntime',
name: config.name,
build: config.buildType,
entrypoint: config.entrypoint as FilePath,
codeLocation: config.codeLocation as DirectoryPath,
runtimeVersion: config.pythonVersion,
networkMode: 'PUBLIC',
networkMode: config.networkMode ?? 'PUBLIC',
};
if (config.networkMode === 'VPC' && config.subnets && config.securityGroups) {
agent.networkConfig = {
subnets: config.subnets,
securityGroups: config.securityGroups,
};
}
return agent;
}

/**
Expand All @@ -76,6 +83,9 @@ function mapAddAgentConfigToGenerateConfig(config: AddAgentConfig): GenerateConf
modelProvider: config.modelProvider,
memory: config.memory,
language: config.language,
networkMode: config.networkMode,
subnets: config.subnets,
securityGroups: config.securityGroups,
};
}

Expand Down
3 changes: 3 additions & 0 deletions src/cli/tui/screens/create/useCreateFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ export function useCreateFlow(cwd: string): CreateFlowState {
memory: addAgentConfig.memory,
language: addAgentConfig.language,
apiKey: addAgentConfig.apiKey,
networkMode: addAgentConfig.networkMode,
subnets: addAgentConfig.subnets,
securityGroups: addAgentConfig.securityGroups,
};

logger.logSubStep(`Framework: ${generateConfig.sdk}`);
Expand Down
7 changes: 7 additions & 0 deletions src/cli/tui/screens/dev/DevScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export function DevScreen(props: DevScreenProps) {
stop,
logFilePath,
hasMemory,
hasVpc,
modelProvider,
} = useDevServer({
workingDir,
Expand Down Expand Up @@ -444,6 +445,12 @@ export function DevScreen(props: DevScreenProps) {
AgentCore memory is not available when running locally. To test memory, deploy and use invoke.
</Text>
)}
{hasVpc && (
<Text color="yellow">
This agent uses VPC network mode. Local dev server runs outside your VPC. Network behavior may differ from
deployed environment.
</Text>
)}
</Box>
);

Expand Down
Loading
Loading