Skip to content

Commit 612c7ac

Browse files
committed
refactor(import): address PR review feedback
- Add `red` to ANSI constants, replace inline escape codes - Type GetEvaluatorResult.level as EvaluationLevel at boundary - Combine ARN_RESOURCE_TYPE_MAP, collectionKeyMap, idFieldMap into single RESOURCE_TYPE_CONFIG to prevent drift - Export IMPORTABLE_RESOURCES as const array, derive type from it, replace || chains with .includes() - Fix samplingPercentage === 0 false positive (use == null) - Document closure state sequencing contract on descriptor hooks
1 parent 5f2a213 commit 612c7ac

File tree

12 files changed

+60
-49
lines changed

12 files changed

+60
-49
lines changed

src/cli/aws/agentcore-control.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { EvaluationLevel } from '../../schema/schemas/primitives/evaluator';
12
import { getCredentialProvider } from './account';
23
import {
34
BedrockAgentCoreControlClient,
@@ -455,7 +456,7 @@ export interface GetEvaluatorResult {
455456
evaluatorId: string;
456457
evaluatorArn: string;
457458
evaluatorName: string;
458-
level: string;
459+
level: EvaluationLevel;
459460
status: string;
460461
description?: string;
461462
evaluatorConfig?: {
@@ -536,7 +537,7 @@ export async function getEvaluator(options: GetEvaluatorOptions): Promise<GetEva
536537
evaluatorId: response.evaluatorId,
537538
evaluatorArn: response.evaluatorArn ?? '',
538539
evaluatorName: response.evaluatorName ?? '',
539-
level: response.level ?? 'SESSION',
540+
level: (response.level ?? 'SESSION') as EvaluationLevel,
540541
status: response.status ?? 'UNKNOWN',
541542
description: response.description,
542543
evaluatorConfig,

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,14 @@ describe('toEvaluatorSpec', () => {
211211
expect(result.tags).toBeUndefined();
212212
});
213213

214-
it('defaults level to SESSION when not provided', () => {
215-
const detail: GetEvaluatorResult = {
214+
it('defaults level to SESSION when empty string reaches toEvaluatorSpec', () => {
215+
// In practice, getEvaluator defaults empty level to 'SESSION' via the as EvaluationLevel cast.
216+
// This tests the fallback in toEvaluatorSpec for defensive coverage.
217+
const detail = {
216218
evaluatorId: 'eval-no-level',
217219
evaluatorArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:evaluator/eval-no-level',
218220
evaluatorName: 'no_level_eval',
219-
level: '',
221+
level: '' as GetEvaluatorResult['level'],
220222
status: 'ACTIVE',
221223
evaluatorConfig: {
222224
llmAsAJudge: {
@@ -225,11 +227,10 @@ describe('toEvaluatorSpec', () => {
225227
ratingScale: { numerical: [{ value: 1, label: 'Low', definition: 'Low' }] },
226228
},
227229
},
228-
};
230+
} as GetEvaluatorResult;
229231

230-
// level defaults to 'SESSION' via the ?? in getEvaluator, but toEvaluatorSpec takes what it gets
231232
const result = toEvaluatorSpec(detail, 'no_level_eval');
232-
expect(result.level).toBe('');
233+
expect(result.level).toBe('SESSION');
233234
});
234235
});
235236

src/cli/commands/import/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/;
33

44
/** ANSI escape codes for console output. */
55
export const ANSI = {
6+
red: '\x1b[31m',
67
green: '\x1b[32m',
78
yellow: '\x1b[33m',
89
cyan: '\x1b[36m',

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type { Command } from '@commander-js/extra-typings';
1616
* Map an AWS GetEvaluator response to the CLI Evaluator spec format.
1717
*/
1818
export function toEvaluatorSpec(detail: GetEvaluatorResult, localName: string): Evaluator {
19-
const level = (detail.level ?? 'SESSION') as Evaluator['level'];
19+
const level = detail.level || 'SESSION';
2020

2121
let config: Evaluator['config'];
2222

@@ -143,7 +143,7 @@ export function registerImportEvaluator(importCmd: Command): void {
143143
console.log(` ID: ${result.resourceId}`);
144144
console.log('');
145145
} else {
146-
console.error(`\n\x1b[31m[error]${ANSI.reset} ${result.error}`);
146+
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`);
147147
if (result.logPath) {
148148
console.error(`Log: ${result.logPath}`);
149149
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export function registerImportMemory(importCmd: Command): void {
121121
console.log(` ID: ${result.resourceId}`);
122122
console.log('');
123123
} else {
124-
console.error(`\n\x1b[31m[error]${ANSI.reset} ${result.error}`);
124+
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`);
125125
if (result.logPath) {
126126
console.error(`Log: ${result.logPath}`);
127127
}

src/cli/commands/import/import-online-eval.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function toOnlineEvalConfigSpec(
3232
agentName: string,
3333
evaluatorArns: string[]
3434
): OnlineEvalConfig {
35-
if (!detail.samplingPercentage) {
35+
if (detail.samplingPercentage == null) {
3636
throw new Error(`Online eval config "${detail.configName}" has no sampling configuration. Cannot import.`);
3737
}
3838

@@ -59,6 +59,7 @@ function buildEvaluatorArns(evaluatorIds: string[], region: string, account: str
5959
* Create an online-eval descriptor with closed-over state for reference resolution.
6060
*/
6161
function createOnlineEvalDescriptor(): ResourceImportDescriptor<GetOnlineEvalConfigResult, OnlineEvalConfigSummary> {
62+
// Set by beforeConfigWrite, read by addToProjectSpec. Ordering guaranteed by executeResourceImport.
6263
let resolvedAgentName = '';
6364
let resolvedEvaluatorArns: string[] = [];
6465

@@ -207,7 +208,7 @@ export function registerImportOnlineEval(importCmd: Command): void {
207208
console.log(` ID: ${result.resourceId}`);
208209
console.log('');
209210
} else {
210-
console.error(`\n\x1b[31m[error]${ANSI.reset} ${result.error}`);
211+
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`);
211212
if (result.logPath) {
212213
console.error(`Log: ${result.logPath}`);
213214
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export function registerImportRuntime(importCmd: Command): void {
225225
console.log(` agentcore invoke ${ANSI.dim}Test your agent${ANSI.reset}`);
226226
console.log('');
227227
} else {
228-
console.error(`\n\x1b[31m[error]${ANSI.reset} ${result.error}`);
228+
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`);
229229
if (result.logPath) {
230230
console.error(`Log: ${result.logPath}`);
231231
}

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

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,23 @@ export interface ParsedArn {
212212
const ARN_PATTERN =
213213
/^arn:aws:bedrock-agentcore:([^:]+):([^:]+):(runtime|memory|evaluator|online-evaluation-config)\/(.+)$/;
214214

215-
/** Map from ImportableResourceType to the resource type string used in ARNs. */
216-
const ARN_RESOURCE_TYPE_MAP: Record<ImportableResourceType, string> = {
217-
runtime: 'runtime',
218-
memory: 'memory',
219-
evaluator: 'evaluator',
220-
'online-eval': 'online-evaluation-config',
215+
/** Unified config for each importable resource type — ARN mapping, deployed state keys. */
216+
const RESOURCE_TYPE_CONFIG: Record<
217+
ImportableResourceType,
218+
{
219+
arnType: string;
220+
collectionKey: string;
221+
idField: string;
222+
}
223+
> = {
224+
runtime: { arnType: 'runtime', collectionKey: 'runtimes', idField: 'runtimeId' },
225+
memory: { arnType: 'memory', collectionKey: 'memories', idField: 'memoryId' },
226+
evaluator: { arnType: 'evaluator', collectionKey: 'evaluators', idField: 'evaluatorId' },
227+
'online-eval': {
228+
arnType: 'online-evaluation-config',
229+
collectionKey: 'onlineEvalConfigs',
230+
idField: 'onlineEvaluationConfigId',
231+
},
221232
};
222233

223234
/**
@@ -230,7 +241,7 @@ export function parseAndValidateArn(
230241
target: { region: string; account: string }
231242
): ParsedArn {
232243
const match = ARN_PATTERN.exec(arn);
233-
const expectedArnType = ARN_RESOURCE_TYPE_MAP[expectedResourceType];
244+
const expectedArnType = RESOURCE_TYPE_CONFIG[expectedResourceType].arnType;
234245
if (!match) {
235246
throw new Error(
236247
`Invalid ARN format: "${arn}". Expected format: arn:aws:bedrock-agentcore:<region>:<account>:${expectedArnType}/<id>`
@@ -289,23 +300,10 @@ export async function findResourceInDeployedState(
289300
const targetState = state.targets?.[targetName];
290301
if (!targetState?.resources) return undefined;
291302

292-
const collectionKeyMap: Record<ImportableResourceType, string> = {
293-
runtime: 'runtimes',
294-
memory: 'memories',
295-
evaluator: 'evaluators',
296-
'online-eval': 'onlineEvalConfigs',
297-
};
298-
const idFieldMap: Record<ImportableResourceType, string> = {
299-
runtime: 'runtimeId',
300-
memory: 'memoryId',
301-
evaluator: 'evaluatorId',
302-
'online-eval': 'onlineEvaluationConfigId',
303-
};
303+
const { collectionKey, idField } = RESOURCE_TYPE_CONFIG[resourceType];
304304

305-
const collection = targetState.resources[collectionKeyMap[resourceType]];
305+
const collection = targetState.resources[collectionKey];
306306
if (!collection) return undefined;
307-
308-
const idField = idFieldMap[resourceType];
309307
for (const [name, entry] of Object.entries(collection)) {
310308
if ((entry as any)[idField] === resourceId) return name;
311309
}

src/cli/commands/import/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ export interface ParsedStarterToolkitConfig {
7272

7373
/**
7474
* Resource types supported by the import subcommands.
75+
* Use the array for runtime checks (e.g., IMPORTABLE_RESOURCES.includes(x)).
7576
*/
76-
export type ImportableResourceType = 'runtime' | 'memory' | 'evaluator' | 'online-eval';
77+
export const IMPORTABLE_RESOURCES = ['runtime', 'memory', 'evaluator', 'online-eval'] as const;
78+
export type ImportableResourceType = (typeof IMPORTABLE_RESOURCES)[number];
7779

7880
/**
7981
* Resource to be imported via CloudFormation IMPORT change set.
@@ -203,7 +205,10 @@ export interface ResourceImportDescriptor<TDetail, TSummary> {
203205
/** Get the array of existing resource names from the project spec. */
204206
getExistingNames: (projectSpec: AgentCoreProjectSpec) => string[];
205207

206-
/** Convert the AWS detail to local spec and add it to the project spec. */
208+
/**
209+
* Convert the AWS detail to local spec and add it to the project spec.
210+
* Called after beforeConfigWrite — descriptor factories may rely on state set during that hook.
211+
*/
207212
addToProjectSpec: (detail: TDetail, localName: string, projectSpec: AgentCoreProjectSpec) => void;
208213

209214
// ---- CFN template matching ----
@@ -226,6 +231,8 @@ export interface ResourceImportDescriptor<TDetail, TSummary> {
226231

227232
/**
228233
* Called after detail fetch + name validation but before config write.
234+
* Always runs before addToProjectSpec — descriptor factories can use this
235+
* to set closed-over state that addToProjectSpec later reads.
229236
* Return an ImportResourceResult to abort, or void to continue.
230237
*/
231238
beforeConfigWrite?: (ctx: BeforeWriteContext<TDetail>) => Promise<ImportResourceResult | void>;

src/cli/tui/screens/import/ArnInputScreen.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ImportableResourceType } from '../../../commands/import/types';
12
import { Panel } from '../../components/Panel';
23
import { Screen } from '../../components/Screen';
34
import { TextInput } from '../../components/TextInput';
@@ -13,7 +14,7 @@ function validateArn(value: string): true | string {
1314
}
1415

1516
interface ArnInputScreenProps {
16-
resourceType: 'runtime' | 'memory' | 'evaluator' | 'online-eval';
17+
resourceType: ImportableResourceType;
1718
onSubmit: (arn: string) => void;
1819
onExit: () => void;
1920
}

0 commit comments

Comments
 (0)