Skip to content

Commit 170bf40

Browse files
committed
feat: add VPC network mode to schema
Fix NetworkModeSchema enum from PUBLIC|PRIVATE to PUBLIC|VPC to match the AWS API. Add NetworkConfigSchema for subnet and security group validation, and networkConfig field to AgentEnvSpec with cross-field validation requiring networkConfig when networkMode is VPC. Changes: - Fix NetworkModeSchema enum: PRIVATE → VPC - Add NetworkConfigSchema (subnet/SG ID regex, min/max array bounds) - Add networkConfig optional field to AgentEnvSpec - Add superRefine cross-field validation - Update LLM-compacted schema documentation - Add 13 new unit tests for VPC schema validation
1 parent 0b39e45 commit 170bf40

7 files changed

Lines changed: 159 additions & 25 deletions

File tree

src/schema/__tests__/constants.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ describe('NetworkModeSchema', () => {
7474
expect(NetworkModeSchema.safeParse('PUBLIC').success).toBe(true);
7575
});
7676

77-
it('accepts PRIVATE', () => {
78-
expect(NetworkModeSchema.safeParse('PRIVATE').success).toBe(true);
77+
it('accepts VPC', () => {
78+
expect(NetworkModeSchema.safeParse('VPC').success).toBe(true);
7979
});
8080

8181
it('rejects other modes', () => {
82-
expect(NetworkModeSchema.safeParse('VPC').success).toBe(false);
82+
expect(NetworkModeSchema.safeParse('PRIVATE').success).toBe(false);
8383
});
8484
});
8585

src/schema/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,5 @@ export type NodeRuntime = z.infer<typeof NodeRuntimeSchema>;
139139
export const RuntimeVersionSchema = z.union([PythonRuntimeSchema, NodeRuntimeSchema]);
140140
export type RuntimeVersion = z.infer<typeof RuntimeVersionSchema>;
141141

142-
export const NetworkModeSchema = z.enum(['PUBLIC', 'PRIVATE']);
142+
export const NetworkModeSchema = z.enum(['PUBLIC', 'VPC']);
143143
export type NetworkMode = z.infer<typeof NetworkModeSchema>;

src/schema/llm-compacted/agentcore.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,19 @@ type BuildType = 'CodeZip' | 'Container';
2626
type PythonRuntime = 'PYTHON_3_10' | 'PYTHON_3_11' | 'PYTHON_3_12' | 'PYTHON_3_13';
2727
type NodeRuntime = 'NODE_18' | 'NODE_20' | 'NODE_22';
2828
type RuntimeVersion = PythonRuntime | NodeRuntime;
29-
type NetworkMode = 'PUBLIC' | 'PRIVATE';
29+
type NetworkMode = 'PUBLIC' | 'VPC';
3030
type MemoryStrategyType = 'SEMANTIC' | 'SUMMARIZATION' | 'USER_PREFERENCE';
3131
type ModelProvider = 'Bedrock' | 'Gemini' | 'OpenAI' | 'Anthropic';
3232

33+
// ─────────────────────────────────────────────────────────────────────────────
34+
// NETWORK CONFIG
35+
// ─────────────────────────────────────────────────────────────────────────────
36+
37+
interface NetworkConfig {
38+
subnets: string[]; // @regex ^subnet-[0-9a-zA-Z]{8,17}$ @min 1 @max 16
39+
securityGroups: string[]; // @regex ^sg-[0-9a-zA-Z]{8,17}$ @min 1 @max 16
40+
}
41+
3342
// ─────────────────────────────────────────────────────────────────────────────
3443
// AGENT
3544
// ─────────────────────────────────────────────────────────────────────────────
@@ -43,6 +52,7 @@ interface AgentEnvSpec {
4352
runtimeVersion: RuntimeVersion;
4453
envVars?: EnvVar[];
4554
networkMode?: NetworkMode; // default 'PUBLIC'
55+
networkConfig?: NetworkConfig; // Required when networkMode is 'VPC'
4656
instrumentation?: Instrumentation; // OTel settings
4757
modelProvider?: ModelProvider; // Model provider used by this agent
4858
}

src/schema/llm-compacted/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,4 @@ interface IamPolicyDocument {
145145
type GatewayTargetType = 'lambda' | 'mcpServer' | 'openApiSchema' | 'smithyModel';
146146
type PythonRuntime = 'PYTHON_3_10' | 'PYTHON_3_11' | 'PYTHON_3_12' | 'PYTHON_3_13';
147147
type NodeRuntime = 'NODE_18' | 'NODE_20' | 'NODE_22';
148-
type NetworkMode = 'PUBLIC' | 'PRIVATE';
148+
type NetworkMode = 'PUBLIC' | 'VPC';

src/schema/schemas/__tests__/agent-env.test.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
EnvVarSchema,
88
GatewayNameSchema,
99
InstrumentationSchema,
10+
NetworkConfigSchema,
1011
} from '../agent-env.js';
1112
import { describe, expect, it } from 'vitest';
1213

@@ -235,13 +236,51 @@ describe('AgentEnvSpecSchema', () => {
235236

236237
it('accepts agent with network mode', () => {
237238
expect(AgentEnvSpecSchema.safeParse({ ...validPythonAgent, networkMode: 'PUBLIC' }).success).toBe(true);
238-
expect(AgentEnvSpecSchema.safeParse({ ...validPythonAgent, networkMode: 'PRIVATE' }).success).toBe(true);
239+
expect(
240+
AgentEnvSpecSchema.safeParse({
241+
...validPythonAgent,
242+
networkMode: 'VPC',
243+
networkConfig: {
244+
subnets: ['subnet-12345678'],
245+
securityGroups: ['sg-12345678'],
246+
},
247+
}).success
248+
).toBe(true);
239249
});
240250

241251
it('rejects invalid network mode', () => {
252+
expect(AgentEnvSpecSchema.safeParse({ ...validPythonAgent, networkMode: 'PRIVATE' }).success).toBe(false);
253+
});
254+
255+
it('rejects VPC mode without networkConfig', () => {
242256
expect(AgentEnvSpecSchema.safeParse({ ...validPythonAgent, networkMode: 'VPC' }).success).toBe(false);
243257
});
244258

259+
it('rejects networkConfig without VPC mode', () => {
260+
expect(
261+
AgentEnvSpecSchema.safeParse({
262+
...validPythonAgent,
263+
networkMode: 'PUBLIC',
264+
networkConfig: {
265+
subnets: ['subnet-12345678'],
266+
securityGroups: ['sg-12345678'],
267+
},
268+
}).success
269+
).toBe(false);
270+
});
271+
272+
it('rejects networkConfig with missing networkMode', () => {
273+
expect(
274+
AgentEnvSpecSchema.safeParse({
275+
...validPythonAgent,
276+
networkConfig: {
277+
subnets: ['subnet-12345678'],
278+
securityGroups: ['sg-12345678'],
279+
},
280+
}).success
281+
).toBe(false);
282+
});
283+
245284
it('accepts agent with instrumentation config', () => {
246285
const result = AgentEnvSpecSchema.safeParse({
247286
...validPythonAgent,
@@ -259,3 +298,53 @@ describe('AgentEnvSpecSchema', () => {
259298
expect(AgentEnvSpecSchema.safeParse({ ...validPythonAgent, name: undefined }).success).toBe(false);
260299
});
261300
});
301+
302+
describe('NetworkConfigSchema', () => {
303+
it('accepts valid network config', () => {
304+
const result = NetworkConfigSchema.safeParse({
305+
subnets: ['subnet-12345678'],
306+
securityGroups: ['sg-12345678'],
307+
});
308+
expect(result.success).toBe(true);
309+
});
310+
311+
it('accepts multiple subnets and security groups', () => {
312+
const result = NetworkConfigSchema.safeParse({
313+
subnets: ['subnet-12345678', 'subnet-abcdef12'],
314+
securityGroups: ['sg-12345678', 'sg-abcdef12'],
315+
});
316+
expect(result.success).toBe(true);
317+
});
318+
319+
it('rejects empty subnets array', () => {
320+
const result = NetworkConfigSchema.safeParse({
321+
subnets: [],
322+
securityGroups: ['sg-12345678'],
323+
});
324+
expect(result.success).toBe(false);
325+
});
326+
327+
it('rejects empty security groups array', () => {
328+
const result = NetworkConfigSchema.safeParse({
329+
subnets: ['subnet-12345678'],
330+
securityGroups: [],
331+
});
332+
expect(result.success).toBe(false);
333+
});
334+
335+
it('rejects invalid subnet format', () => {
336+
const result = NetworkConfigSchema.safeParse({
337+
subnets: ['invalid-subnet'],
338+
securityGroups: ['sg-12345678'],
339+
});
340+
expect(result.success).toBe(false);
341+
});
342+
343+
it('rejects invalid security group format', () => {
344+
const result = NetworkConfigSchema.safeParse({
345+
subnets: ['subnet-12345678'],
346+
securityGroups: ['invalid-sg'],
347+
});
348+
expect(result.success).toBe(false);
349+
});
350+
});

src/schema/schemas/__tests__/mcp.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ describe('RuntimeConfigSchema', () => {
238238
}
239239
});
240240

241-
it('accepts explicit PRIVATE networkMode', () => {
242-
const result = RuntimeConfigSchema.safeParse({ ...validRuntime, networkMode: 'PRIVATE' });
241+
it('accepts explicit VPC networkMode', () => {
242+
const result = RuntimeConfigSchema.safeParse({ ...validRuntime, networkMode: 'VPC' });
243243
expect(result.success).toBe(true);
244244
});
245245

src/schema/schemas/agent-env.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,25 +103,60 @@ export const InstrumentationSchema = z.object({
103103
});
104104
export type Instrumentation = z.infer<typeof InstrumentationSchema>;
105105

106+
/**
107+
* VPC network configuration for agents running in VPC mode.
108+
* Requires at least one subnet and one security group.
109+
*/
110+
export const NetworkConfigSchema = z.object({
111+
subnets: z
112+
.array(z.string().regex(/^subnet-[0-9a-zA-Z]{8,17}$/))
113+
.min(1)
114+
.max(16),
115+
securityGroups: z
116+
.array(z.string().regex(/^sg-[0-9a-zA-Z]{8,17}$/))
117+
.min(1)
118+
.max(16),
119+
});
120+
export type NetworkConfig = z.infer<typeof NetworkConfigSchema>;
121+
106122
/**
107123
* AgentEnvSpec - represents an AgentCore Runtime.
108124
* This is a top-level resource in the schema.
109125
*/
110-
export const AgentEnvSpecSchema = z.object({
111-
type: AgentTypeSchema,
112-
name: AgentNameSchema,
113-
build: BuildTypeSchema,
114-
entrypoint: EntrypointSchema,
115-
codeLocation: DirectoryPathSchema,
116-
runtimeVersion: RuntimeVersionSchemaFromConstants,
117-
/** Environment variables to set on the runtime */
118-
envVars: z.array(EnvVarSchema).optional(),
119-
/** Network mode for the runtime. Defaults to PUBLIC. */
120-
networkMode: NetworkModeSchema.optional(),
121-
/** Instrumentation settings for observability. Defaults to OTel enabled. */
122-
instrumentation: InstrumentationSchema.optional(),
123-
/** Model provider used by this agent. Optional for backwards compatibility. */
124-
modelProvider: ModelProviderSchema.optional(),
125-
});
126+
export const AgentEnvSpecSchema = z
127+
.object({
128+
type: AgentTypeSchema,
129+
name: AgentNameSchema,
130+
build: BuildTypeSchema,
131+
entrypoint: EntrypointSchema,
132+
codeLocation: DirectoryPathSchema,
133+
runtimeVersion: RuntimeVersionSchemaFromConstants,
134+
/** Environment variables to set on the runtime */
135+
envVars: z.array(EnvVarSchema).optional(),
136+
/** Network mode for the runtime. Defaults to PUBLIC. */
137+
networkMode: NetworkModeSchema.optional(),
138+
/** VPC network configuration. Required when networkMode is VPC. */
139+
networkConfig: NetworkConfigSchema.optional(),
140+
/** Instrumentation settings for observability. Defaults to OTel enabled. */
141+
instrumentation: InstrumentationSchema.optional(),
142+
/** Model provider used by this agent. Optional for backwards compatibility. */
143+
modelProvider: ModelProviderSchema.optional(),
144+
})
145+
.superRefine((data, ctx) => {
146+
if (data.networkMode === 'VPC' && !data.networkConfig) {
147+
ctx.addIssue({
148+
code: 'custom',
149+
path: ['networkConfig'],
150+
message: 'networkConfig is required when networkMode is VPC',
151+
});
152+
}
153+
if (data.networkMode !== 'VPC' && data.networkConfig) {
154+
ctx.addIssue({
155+
code: 'custom',
156+
path: ['networkConfig'],
157+
message: 'networkConfig is only allowed when networkMode is VPC',
158+
});
159+
}
160+
});
126161

127162
export type AgentEnvSpec = z.infer<typeof AgentEnvSpecSchema>;

0 commit comments

Comments
 (0)