Skip to content

Commit 9bc2d70

Browse files
committed
feat(evaluator): Add kmsKeyArn support for custom evaluator
1 parent a778fb5 commit 9bc2d70

9 files changed

Lines changed: 109 additions & 1 deletion

File tree

src/cli/aws/agentcore-control.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ export interface GetEvaluatorResult {
463463
llmAsAJudge?: GetEvaluatorLlmConfig;
464464
codeBased?: GetEvaluatorCodeBasedConfig;
465465
};
466+
kmsKeyArn?: string;
466467
tags?: Record<string, string>;
467468
}
468469

@@ -541,6 +542,7 @@ export async function getEvaluator(options: GetEvaluatorOptions): Promise<GetEva
541542
status: response.status ?? 'UNKNOWN',
542543
description: response.description,
543544
evaluatorConfig,
545+
kmsKeyArn: response.kmsKeyArn,
544546
tags,
545547
};
546548
}

src/cli/commands/import/__tests__/import-evaluator.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,49 @@ describe('toEvaluatorSpec', () => {
210210

211211
expect(result.tags).toBeUndefined();
212212
});
213+
214+
it('forwards kmsKeyArn when present', () => {
215+
const detail: GetEvaluatorResult = {
216+
evaluatorId: 'eval-kms',
217+
evaluatorArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:evaluator/eval-kms',
218+
evaluatorName: 'kms_eval',
219+
level: 'SESSION',
220+
status: 'ACTIVE',
221+
evaluatorConfig: {
222+
llmAsAJudge: {
223+
model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
224+
instructions: 'Evaluate',
225+
ratingScale: { numerical: [{ value: 1, label: 'Low', definition: 'Low' }] },
226+
},
227+
},
228+
kmsKeyArn: 'arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012',
229+
};
230+
231+
const result = toEvaluatorSpec(detail, 'kms_eval');
232+
233+
expect(result.kmsKeyArn).toBe('arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012');
234+
});
235+
236+
it('omits kmsKeyArn when not present', () => {
237+
const detail: GetEvaluatorResult = {
238+
evaluatorId: 'eval-no-kms',
239+
evaluatorArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:evaluator/eval-no-kms',
240+
evaluatorName: 'no_kms_eval',
241+
level: 'SESSION',
242+
status: 'ACTIVE',
243+
evaluatorConfig: {
244+
llmAsAJudge: {
245+
model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
246+
instructions: 'Evaluate',
247+
ratingScale: { numerical: [{ value: 1, label: 'Low', definition: 'Low' }] },
248+
},
249+
},
250+
};
251+
252+
const result = toEvaluatorSpec(detail, 'no_kms_eval');
253+
254+
expect(result.kmsKeyArn).toBeUndefined();
255+
});
213256
});
214257

215258
// ============================================================================

src/cli/commands/import/import-evaluator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function toEvaluatorSpec(detail: GetEvaluatorResult, localName: string):
4949
level,
5050
...(detail.description && { description: detail.description }),
5151
config,
52+
...(detail.kmsKeyArn && { kmsKeyArn: detail.kmsKeyArn }),
5253
...(detail.tags && Object.keys(detail.tags).length > 0 && { tags: detail.tags }),
5354
};
5455
}

src/cli/primitives/EvaluatorPrimitive.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface AddEvaluatorOptions {
2323
level: EvaluationLevel;
2424
description?: string;
2525
config: EvaluatorConfig;
26+
kmsKeyArn?: string;
2627
}
2728

2829
export type RemovableEvaluator = RemovableResource;
@@ -182,6 +183,7 @@ export class EvaluatorPrimitive extends BasePrimitive<AddEvaluatorOptions, Remov
182183
'--config <path>',
183184
'Path to evaluator config JSON file (overrides --model, --instructions, --rating-scale) [non-interactive]'
184185
)
186+
.option('--kms-key-arn <arn>', 'KMS key ARN for evaluator encryption (optional)')
185187
.option('--json', 'Output as JSON [non-interactive]')
186188
.action(
187189
async (cliOptions: {
@@ -194,6 +196,7 @@ export class EvaluatorPrimitive extends BasePrimitive<AddEvaluatorOptions, Remov
194196
lambdaArn?: string;
195197
timeout?: string;
196198
config?: string;
199+
kmsKeyArn?: string;
197200
json?: boolean;
198201
}) => {
199202
try {
@@ -292,10 +295,22 @@ export class EvaluatorPrimitive extends BasePrimitive<AddEvaluatorOptions, Remov
292295
};
293296
}
294297

298+
if (
299+
cliOptions.kmsKeyArn &&
300+
!/^arn:aws(?:|-cn|-us-gov):kms:[a-zA-Z0-9-]*:[0-9]{12}:key\/[a-zA-Z0-9-]{36}$/.test(
301+
cliOptions.kmsKeyArn
302+
)
303+
) {
304+
fail(
305+
'--kms-key-arn must be a valid KMS key ARN (e.g. arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012)'
306+
);
307+
}
308+
295309
const result = await this.add({
296310
name: cliOptions.name!,
297311
level: levelResult.data!,
298312
config: configJson,
313+
kmsKeyArn: cliOptions.kmsKeyArn,
299314
});
300315

301316
if (cliOptions.json) {
@@ -385,6 +400,7 @@ export class EvaluatorPrimitive extends BasePrimitive<AddEvaluatorOptions, Remov
385400
level: options.level,
386401
...(options.description && { description: options.description }),
387402
config: options.config,
403+
...(options.kmsKeyArn && { kmsKeyArn: options.kmsKeyArn }),
388404
};
389405

390406
project.evaluators.push(evaluator);

src/cli/tui/hooks/useCreateEvaluator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface CreateEvaluatorConfig {
66
name: string;
77
level: string;
88
config: EvaluatorConfig;
9+
kmsKeyArn?: string;
910
}
1011

1112
export function useCreateEvaluator() {
@@ -20,6 +21,7 @@ export function useCreateEvaluator() {
2021
name: config.name,
2122
level: config.level as 'SESSION' | 'TRACE' | 'TOOL_CALL',
2223
config: config.config,
24+
kmsKeyArn: config.kmsKeyArn,
2325
});
2426
if (!addResult.success) {
2527
throw new Error(addResult.error ?? 'Failed to create evaluator');

src/cli/tui/screens/evaluator/AddEvaluatorScreen.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames
9191
const isRatingScaleCustomStep = wizard.step === 'ratingScale-custom';
9292
const isLambdaArnStep = wizard.step === 'lambda-arn';
9393
const isTimeoutStep = wizard.step === 'timeout';
94+
const isKmsKeyArnStep = wizard.step === 'kms-key-arn';
9495
const isConfirmStep = wizard.step === 'confirm';
9596

9697
const evaluatorTypeNav = useListNavigation({
@@ -163,6 +164,8 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames
163164

164165
// Build confirm fields based on evaluator type
165166
const confirmFields = useMemo(() => {
167+
const kmsField = wizard.config.kmsKeyArn ? [{ label: 'KMS Key ARN', value: wizard.config.kmsKeyArn }] : [];
168+
166169
if (wizard.evaluatorType === 'llm-as-a-judge') {
167170
const llm = wizard.config.config.llmAsAJudge!;
168171
return [
@@ -175,6 +178,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames
175178
value: llm.instructions.length > 60 ? llm.instructions.slice(0, 60) + '...' : llm.instructions,
176179
},
177180
{ label: 'Rating Scale', value: formatRatingScale(llm.ratingScale) },
181+
...kmsField,
178182
];
179183
}
180184

@@ -187,6 +191,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames
187191
{ label: 'Code', value: managed.codeLocation },
188192
{ label: 'Entrypoint', value: managed.entrypoint },
189193
{ label: 'Timeout', value: `${managed.timeoutSeconds}s` },
194+
...kmsField,
190195
];
191196
}
192197

@@ -197,6 +202,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames
197202
{ label: 'Name', value: wizard.config.name },
198203
{ label: 'Level', value: wizard.config.level },
199204
{ label: 'Lambda ARN', value: external.lambdaArn },
205+
...kmsField,
200206
];
201207
}, [wizard.evaluatorType, wizard.codeBasedType, wizard.config]);
202208

@@ -374,6 +380,21 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames
374380
/>
375381
)}
376382

383+
{isKmsKeyArnStep && (
384+
<TextInput
385+
key="kms-key-arn"
386+
prompt="KMS key ARN for encryption (optional, press Enter to skip)"
387+
initialValue=""
388+
onSubmit={wizard.setKmsKeyArn}
389+
onCancel={() => wizard.goBack()}
390+
customValidation={value =>
391+
value === '' ||
392+
/^arn:aws(?:|-cn|-us-gov):kms:[a-zA-Z0-9-]*:[0-9]{12}:key\/[a-zA-Z0-9-]{36}$/.test(value) ||
393+
'Must be a valid KMS key ARN (e.g. arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012)'
394+
}
395+
/>
396+
)}
397+
377398
{isConfirmStep && <ConfirmReview fields={confirmFields} />}
378399
</Panel>
379400
</Screen>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ export type AddEvaluatorStep =
2020
| 'ratingScale-custom'
2121
| 'lambda-arn'
2222
| 'timeout'
23+
| 'kms-key-arn'
2324
| 'confirm';
2425

2526
export interface AddEvaluatorConfig {
2627
name: string;
2728
level: EvaluationLevel;
2829
config: EvaluatorConfig;
30+
kmsKeyArn?: string;
2931
}
3032

3133
export const EVALUATOR_STEP_LABELS: Record<AddEvaluatorStep, string> = {
@@ -41,6 +43,7 @@ export const EVALUATOR_STEP_LABELS: Record<AddEvaluatorStep, string> = {
4143
'ratingScale-custom': 'Scale',
4244
'lambda-arn': 'Lambda',
4345
timeout: 'Timeout',
46+
'kms-key-arn': 'KMS Key',
4447
confirm: 'Confirm',
4548
};
4649

src/cli/tui/screens/evaluator/useAddEvaluatorWizard.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const LLM_STEPS: AddEvaluatorStep[] = [
2222
'model',
2323
'instructions',
2424
'ratingScale',
25+
'kms-key-arn',
2526
'confirm',
2627
];
2728
const CODE_MANAGED_STEPS: AddEvaluatorStep[] = [
@@ -30,6 +31,7 @@ const CODE_MANAGED_STEPS: AddEvaluatorStep[] = [
3031
'name',
3132
'level',
3233
'timeout',
34+
'kms-key-arn',
3335
'confirm',
3436
];
3537
const CODE_EXTERNAL_STEPS: AddEvaluatorStep[] = [
@@ -38,6 +40,7 @@ const CODE_EXTERNAL_STEPS: AddEvaluatorStep[] = [
3840
'name',
3941
'level',
4042
'lambda-arn',
43+
'kms-key-arn',
4144
'confirm',
4245
];
4346

@@ -80,6 +83,7 @@ export function useAddEvaluatorWizard() {
8083
const [lambdaArn, setLambdaArnState] = useState('');
8184
const [timeout, setTimeoutState] = useState(DEFAULT_CODE_TIMEOUT);
8285
const [customRatingScaleType, setCustomRatingScaleType] = useState<CustomRatingScaleType>('numerical');
86+
const [kmsKeyArn, setKmsKeyArnState] = useState('');
8387
const [step, setStep] = useState<AddEvaluatorStep>('evaluator-type');
8488

8589
const steps = useMemo(() => getSteps(evaluatorType, codeBasedType), [evaluatorType, codeBasedType]);
@@ -109,11 +113,13 @@ export function useAddEvaluatorWizard() {
109113

110114
// Build the final config based on current state
111115
const config: AddEvaluatorConfig = useMemo(() => {
116+
const kms = kmsKeyArn || undefined;
112117
if (evaluatorType === 'llm-as-a-judge') {
113118
return {
114119
name,
115120
level,
116121
config: { llmAsAJudge: llmConfig },
122+
...(kms && { kmsKeyArn: kms }),
117123
};
118124
}
119125

@@ -126,6 +132,7 @@ export function useAddEvaluatorWizard() {
126132
external: { lambdaArn },
127133
},
128134
},
135+
...(kms && { kmsKeyArn: kms }),
129136
};
130137
}
131138

@@ -143,8 +150,9 @@ export function useAddEvaluatorWizard() {
143150
},
144151
},
145152
},
153+
...(kms && { kmsKeyArn: kms }),
146154
};
147-
}, [evaluatorType, codeBasedType, name, level, llmConfig, lambdaArn, timeout]);
155+
}, [evaluatorType, codeBasedType, name, level, llmConfig, lambdaArn, timeout, kmsKeyArn]);
148156

149157
const selectEvaluatorType = useCallback((type: EvaluatorTypeId) => {
150158
setEvaluatorType(type);
@@ -256,6 +264,15 @@ export function useAddEvaluatorWizard() {
256264
[nextStep]
257265
);
258266

267+
const setKmsKeyArn = useCallback(
268+
(arn: string) => {
269+
setKmsKeyArnState(arn);
270+
const next = nextStep('kms-key-arn');
271+
if (next) setStep(next);
272+
},
273+
[nextStep]
274+
);
275+
259276
const reset = useCallback(() => {
260277
setEvaluatorType('code-based');
261278
setCodeBasedType('managed');
@@ -264,6 +281,7 @@ export function useAddEvaluatorWizard() {
264281
setLlmConfig(getDefaultLlmConfig().llmAsAJudge!);
265282
setLambdaArnState('');
266283
setTimeoutState(DEFAULT_CODE_TIMEOUT);
284+
setKmsKeyArnState('');
267285
setStep('evaluator-type');
268286
}, []);
269287

@@ -288,6 +306,7 @@ export function useAddEvaluatorWizard() {
288306
setCustomRatingScale,
289307
setLambdaArn,
290308
setTimeout,
309+
setKmsKeyArn,
291310
reset,
292311
};
293312
}

src/schema/schemas/agentcore-project.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export const EvaluatorSchema = z.object({
200200
level: EvaluationLevelSchema,
201201
description: z.string().optional(),
202202
config: EvaluatorConfigSchema,
203+
kmsKeyArn: z.string().optional(),
203204
tags: TagsSchema.optional(),
204205
});
205206

0 commit comments

Comments
 (0)