Skip to content

Commit 915125d

Browse files
authored
feat: inject $schema URL into generated agentcore.json (#692)
* feat: inject $schema URL into generated agentcore.json * fix: use official schema URL and remove redundant $schema injection * fix: make JSON schema default-field filter recursive for nested objects * chore: remove generated schema test * fix: make default-field filter recursive for nested objects
1 parent 956c248 commit 915125d

10 files changed

Lines changed: 269 additions & 88 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createTestProject, runCLI } from '../src/test-utils/index.js';
2+
import type { TestProject } from '../src/test-utils/index.js';
3+
import { readFile, writeFile } from 'node:fs/promises';
4+
import { join } from 'node:path';
5+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6+
7+
const SCHEMA_URL_PATTERN = /^https:\/\/schema\.agentcore\.aws\.dev\/.+\.json$/;
8+
async function readRawConfig(projectPath: string): Promise<Record<string, unknown>> {
9+
const raw = await readFile(join(projectPath, 'agentcore', 'agentcore.json'), 'utf-8');
10+
return JSON.parse(raw) as Record<string, unknown>;
11+
}
12+
13+
describe('integration: $schema injection in agentcore.json', () => {
14+
let project: TestProject;
15+
16+
beforeAll(async () => {
17+
project = await createTestProject({
18+
language: 'Python',
19+
framework: 'Strands',
20+
modelProvider: 'Bedrock',
21+
memory: 'none',
22+
});
23+
});
24+
25+
afterAll(async () => {
26+
await project.cleanup();
27+
});
28+
29+
it('new project has $schema set to the official URL as the first key', async () => {
30+
const config = await readRawConfig(project.projectPath);
31+
expect(config.$schema).toMatch(SCHEMA_URL_PATTERN);
32+
expect(Object.keys(config)[0]).toBe('$schema');
33+
});
34+
35+
it('$schema persists after adding a resource', async () => {
36+
const memName = `SchemaMem${Date.now().toString().slice(-6)}`;
37+
await runCLI(['add', 'memory', '--name', memName, '--json'], project.projectPath);
38+
39+
const config = await readRawConfig(project.projectPath);
40+
expect(config.$schema).toMatch(SCHEMA_URL_PATTERN);
41+
42+
await runCLI(['remove', 'memory', '--name', memName, '--json'], project.projectPath);
43+
});
44+
45+
it('does not overwrite a custom $schema value', async () => {
46+
const configPath = join(project.projectPath, 'agentcore', 'agentcore.json');
47+
const config = await readRawConfig(project.projectPath);
48+
const customUrl = 'https://example.com/custom-schema.json';
49+
config.$schema = customUrl;
50+
await writeFile(configPath, JSON.stringify(config, null, 2));
51+
52+
const memName = `CustomMem${Date.now().toString().slice(-6)}`;
53+
await runCLI(['add', 'memory', '--name', memName, '--json'], project.projectPath);
54+
55+
const updated = await readRawConfig(project.projectPath);
56+
expect(updated.$schema).toBe(customUrl);
57+
58+
await runCLI(['remove', 'memory', '--name', memName, '--json'], project.projectPath);
59+
});
60+
61+
it('does not inject $schema into a pre-existing project that lacks one', async () => {
62+
const configPath = join(project.projectPath, 'agentcore', 'agentcore.json');
63+
const config = await readRawConfig(project.projectPath);
64+
65+
// Simulate an old project by stripping $schema
66+
delete config.$schema;
67+
await writeFile(configPath, JSON.stringify(config, null, 2));
68+
69+
// Trigger a write
70+
const memName = `OldProj${Date.now().toString().slice(-6)}`;
71+
await runCLI(['add', 'memory', '--name', memName, '--json'], project.projectPath);
72+
73+
const updated = await readRawConfig(project.projectPath);
74+
expect(updated.$schema).toBeUndefined();
75+
76+
await runCLI(['remove', 'memory', '--name', memName, '--json'], project.projectPath);
77+
});
78+
});

schemas/agentcore.schema.v1.json

Lines changed: 136 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"$schema": "http://json-schema.org/draft-07/schema#",
33
"type": "object",
44
"properties": {
5+
"$schema": {
6+
"type": "string"
7+
},
58
"name": {
69
"type": "string",
710
"minLength": 1,
@@ -13,6 +16,11 @@
1316
"minimum": 1,
1417
"maximum": 9007199254740991
1518
},
19+
"managedBy": {
20+
"default": "CDK",
21+
"type": "string",
22+
"enum": ["CDK"]
23+
},
1624
"tags": {
1725
"type": "object",
1826
"propertyNames": {
@@ -124,7 +132,6 @@
124132
"type": "boolean"
125133
}
126134
},
127-
"required": ["enableOtel"],
128135
"additionalProperties": false
129136
},
130137
"modelProvider": {
@@ -142,6 +149,103 @@
142149
"type": "string"
143150
}
144151
},
152+
"authorizerType": {
153+
"type": "string",
154+
"enum": ["AWS_IAM", "CUSTOM_JWT"]
155+
},
156+
"authorizerConfiguration": {
157+
"type": "object",
158+
"properties": {
159+
"customJwtAuthorizer": {
160+
"type": "object",
161+
"properties": {
162+
"discoveryUrl": {
163+
"type": "string",
164+
"format": "uri"
165+
},
166+
"allowedAudience": {
167+
"type": "array",
168+
"items": {
169+
"type": "string",
170+
"minLength": 1
171+
}
172+
},
173+
"allowedClients": {
174+
"type": "array",
175+
"items": {
176+
"type": "string",
177+
"minLength": 1
178+
}
179+
},
180+
"allowedScopes": {
181+
"type": "array",
182+
"items": {
183+
"type": "string",
184+
"minLength": 1
185+
}
186+
},
187+
"customClaims": {
188+
"minItems": 1,
189+
"type": "array",
190+
"items": {
191+
"type": "object",
192+
"properties": {
193+
"inboundTokenClaimName": {
194+
"type": "string",
195+
"minLength": 1,
196+
"maxLength": 255,
197+
"pattern": "^[A-Za-z0-9_.:-]+$"
198+
},
199+
"inboundTokenClaimValueType": {
200+
"type": "string",
201+
"enum": ["STRING", "STRING_ARRAY"]
202+
},
203+
"authorizingClaimMatchValue": {
204+
"type": "object",
205+
"properties": {
206+
"claimMatchOperator": {
207+
"type": "string",
208+
"enum": ["EQUALS", "CONTAINS", "CONTAINS_ANY"]
209+
},
210+
"claimMatchValue": {
211+
"type": "object",
212+
"properties": {
213+
"matchValueString": {
214+
"type": "string",
215+
"minLength": 1,
216+
"maxLength": 255,
217+
"pattern": "^[A-Za-z0-9_.-]+$"
218+
},
219+
"matchValueStringList": {
220+
"minItems": 1,
221+
"maxItems": 255,
222+
"type": "array",
223+
"items": {
224+
"type": "string",
225+
"minLength": 1,
226+
"maxLength": 255,
227+
"pattern": "^[A-Za-z0-9_.-]+$"
228+
}
229+
}
230+
},
231+
"additionalProperties": false
232+
}
233+
},
234+
"required": ["claimMatchOperator", "claimMatchValue"],
235+
"additionalProperties": false
236+
}
237+
},
238+
"required": ["inboundTokenClaimName", "inboundTokenClaimValueType", "authorizingClaimMatchValue"],
239+
"additionalProperties": false
240+
}
241+
}
242+
},
243+
"required": ["discoveryUrl"],
244+
"additionalProperties": false
245+
}
246+
},
247+
"additionalProperties": false
248+
},
145249
"tags": {
146250
"type": "object",
147251
"propertyNames": {
@@ -155,6 +259,22 @@
155259
"maxLength": 256,
156260
"pattern": "^[\\p{L}\\p{N}\\s_.:/=+\\-@]*$"
157261
}
262+
},
263+
"lifecycleConfiguration": {
264+
"type": "object",
265+
"properties": {
266+
"idleRuntimeSessionTimeout": {
267+
"type": "integer",
268+
"minimum": 60,
269+
"maximum": 28800
270+
},
271+
"maxLifetime": {
272+
"type": "integer",
273+
"minimum": 60,
274+
"maximum": 28800
275+
}
276+
},
277+
"additionalProperties": false
158278
}
159279
},
160280
"required": ["type", "name", "build", "entrypoint", "codeLocation", "runtimeVersion"],
@@ -190,7 +310,7 @@
190310
"properties": {
191311
"type": {
192312
"type": "string",
193-
"enum": ["SEMANTIC", "SUMMARIZATION", "USER_PREFERENCE"]
313+
"enum": ["SEMANTIC", "SUMMARIZATION", "USER_PREFERENCE", "EPISODIC"]
194314
},
195315
"name": {
196316
"type": "string",
@@ -206,6 +326,12 @@
206326
"items": {
207327
"type": "string"
208328
}
329+
},
330+
"reflectionNamespaces": {
331+
"type": "array",
332+
"items": {
333+
"type": "string"
334+
}
209335
}
210336
},
211337
"required": ["type"],
@@ -227,7 +353,7 @@
227353
}
228354
}
229355
},
230-
"required": ["type", "name", "eventExpiryDuration", "strategies"],
356+
"required": ["type", "name", "eventExpiryDuration"],
231357
"additionalProperties": false
232358
}
233359
},
@@ -288,7 +414,7 @@
288414
"enum": ["inbound", "outbound"]
289415
}
290416
},
291-
"required": ["type", "name", "discoveryUrl", "vendor"],
417+
"required": ["type", "name"],
292418
"additionalProperties": false
293419
}
294420
]
@@ -645,7 +771,6 @@
645771
"type": "boolean"
646772
}
647773
},
648-
"required": ["enableOtel"],
649774
"additionalProperties": false
650775
},
651776
"networkMode": {
@@ -657,14 +782,7 @@
657782
"type": "string"
658783
}
659784
},
660-
"required": [
661-
"artifact",
662-
"pythonVersion",
663-
"name",
664-
"entrypoint",
665-
"codeLocation",
666-
"networkMode"
667-
],
785+
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation"],
668786
"additionalProperties": false
669787
},
670788
"iamPolicy": {
@@ -710,7 +828,6 @@
710828
}
711829
}
712830
},
713-
"required": ["type"],
714831
"additionalProperties": false
715832
},
716833
"apiGateway": {
@@ -985,7 +1102,7 @@
9851102
}
9861103
}
9871104
},
988-
"required": ["name", "targets", "authorizerType", "enableSemanticSearch", "exceptionLevel"],
1105+
"required": ["name", "targets"],
9891106
"additionalProperties": false
9901107
}
9911108
},
@@ -1084,7 +1201,6 @@
10841201
"type": "boolean"
10851202
}
10861203
},
1087-
"required": ["enableOtel"],
10881204
"additionalProperties": false
10891205
},
10901206
"networkMode": {
@@ -1096,7 +1212,7 @@
10961212
"type": "string"
10971213
}
10981214
},
1099-
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation", "networkMode"],
1215+
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation"],
11001216
"additionalProperties": false
11011217
},
11021218
"iamPolicy": {
@@ -1308,7 +1424,6 @@
13081424
"type": "boolean"
13091425
}
13101426
},
1311-
"required": ["enableOtel"],
13121427
"additionalProperties": false
13131428
},
13141429
"networkMode": {
@@ -1320,7 +1435,7 @@
13201435
"type": "string"
13211436
}
13221437
},
1323-
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation", "networkMode"],
1438+
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation"],
13241439
"additionalProperties": false
13251440
},
13261441
"iamPolicy": {
@@ -1366,7 +1481,6 @@
13661481
}
13671482
}
13681483
},
1369-
"required": ["type"],
13701484
"additionalProperties": false
13711485
},
13721486
"apiGateway": {
@@ -1568,17 +1682,14 @@
15681682
"enum": ["FAIL_ON_ANY_FINDINGS", "IGNORE_ALL_FINDINGS"]
15691683
}
15701684
},
1571-
"required": ["name", "statement", "validationMode"],
1685+
"required": ["name", "statement"],
15721686
"additionalProperties": false
15731687
}
15741688
}
15751689
},
1576-
"required": ["name", "policies"],
1690+
"required": ["name"],
15771691
"additionalProperties": false
15781692
}
1579-
},
1580-
"$schema": {
1581-
"type": "string"
15821693
}
15831694
},
15841695
"required": ["name", "version"],

0 commit comments

Comments
 (0)