Skip to content

Commit e4d2027

Browse files
committed
fix(import): paginate listings, auto-select single result, and preserve runtime config
Three import bugs fixed: 1. listAgentRuntimes/listMemories only fetched one page (max 100). Added listAllAgentRuntimes/listAllMemories that paginate via nextToken. 2. Single-result listing incorrectly showed "Multiple found" error. Now auto-selects when exactly one runtime/memory exists. 3. toAgentEnvSpec dropped env vars, tags, lifecycle config, and request header allowlist. Extended AgentRuntimeDetail and getAgentRuntimeDetail to extract these fields (including ListTagsForResource call for tags), and mapped them in toAgentEnvSpec. Confidence: high Scope-risk: moderate Not-tested: pagination with >100 real resources (no integration test account available)
1 parent 7caee5d commit e4d2027

3 files changed

Lines changed: 145 additions & 29 deletions

File tree

src/cli/aws/agentcore-control.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ export async function listAgentRuntimes(options: ListAgentRuntimesOptions): Prom
100100
};
101101
}
102102

103+
/**
104+
* List all AgentCore Runtimes in the given region, paginating through all pages.
105+
*/
106+
export async function listAllAgentRuntimes(options: { region: string }): Promise<AgentRuntimeSummary[]> {
107+
const runtimes: AgentRuntimeSummary[] = [];
108+
let nextToken: string | undefined;
109+
110+
do {
111+
const result = await listAgentRuntimes({ region: options.region, maxResults: 100, nextToken });
112+
runtimes.push(...result.runtimes);
113+
nextToken = result.nextToken;
114+
} while (nextToken);
115+
116+
return runtimes;
117+
}
118+
103119
export interface GetAgentRuntimeOptions {
104120
region: string;
105121
runtimeId: string;
@@ -127,6 +143,10 @@ export interface AgentRuntimeDetail {
127143
allowedScopes?: string[];
128144
};
129145
};
146+
environmentVariables?: Record<string, string>;
147+
tags?: Record<string, string>;
148+
lifecycleConfiguration?: { idleRuntimeSessionTimeout?: number; maxLifetime?: number };
149+
requestHeaderAllowlist?: string[];
130150
}
131151

132152
/**
@@ -171,6 +191,42 @@ export async function getAgentRuntimeDetail(options: GetAgentRuntimeOptions): Pr
171191
};
172192
}
173193

194+
// Extract environment variables
195+
const environmentVariables =
196+
response.environmentVariables && Object.keys(response.environmentVariables).length > 0
197+
? response.environmentVariables
198+
: undefined;
199+
200+
// Extract lifecycle configuration
201+
const lifecycleConfiguration = response.lifecycleConfiguration
202+
? {
203+
idleRuntimeSessionTimeout: response.lifecycleConfiguration.idleRuntimeSessionTimeout,
204+
maxLifetime: response.lifecycleConfiguration.maxLifetime,
205+
}
206+
: undefined;
207+
208+
// Extract request header allowlist from the union type
209+
let requestHeaderAllowlist: string[] | undefined;
210+
if (response.requestHeaderConfiguration && 'requestHeaderAllowlist' in response.requestHeaderConfiguration) {
211+
const allowlist = response.requestHeaderConfiguration.requestHeaderAllowlist;
212+
if (allowlist && allowlist.length > 0) {
213+
requestHeaderAllowlist = allowlist;
214+
}
215+
}
216+
217+
// Fetch tags via separate API call (same pattern as getMemoryDetail)
218+
let tags: Record<string, string> | undefined;
219+
if (response.agentRuntimeArn) {
220+
try {
221+
const tagsResponse = await client.send(new ListTagsForResourceCommand({ resourceArn: response.agentRuntimeArn }));
222+
if (tagsResponse.tags && Object.keys(tagsResponse.tags).length > 0) {
223+
tags = tagsResponse.tags;
224+
}
225+
} catch {
226+
// Tags are optional — continue without them if the call fails
227+
}
228+
}
229+
174230
return {
175231
agentRuntimeId: response.agentRuntimeId ?? '',
176232
agentRuntimeArn: response.agentRuntimeArn ?? '',
@@ -186,6 +242,10 @@ export async function getAgentRuntimeDetail(options: GetAgentRuntimeOptions): Pr
186242
build: isContainer ? 'Container' : 'CodeZip',
187243
authorizerType,
188244
authorizerConfiguration,
245+
environmentVariables,
246+
tags,
247+
lifecycleConfiguration,
248+
requestHeaderAllowlist,
189249
};
190250
}
191251

@@ -240,6 +300,22 @@ export async function listMemories(options: ListMemoriesOptions): Promise<ListMe
240300
};
241301
}
242302

303+
/**
304+
* List all AgentCore Memories in the given region, paginating through all pages.
305+
*/
306+
export async function listAllMemories(options: { region: string }): Promise<MemorySummary[]> {
307+
const memories: MemorySummary[] = [];
308+
let nextToken: string | undefined;
309+
310+
do {
311+
const result = await listMemories({ region: options.region, maxResults: 100, nextToken });
312+
memories.push(...result.memories);
313+
nextToken = result.nextToken;
314+
} while (nextToken);
315+
316+
return memories;
317+
}
318+
243319
export interface GetMemoryOptions {
244320
region: string;
245321
memoryId: string;

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

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Memory } from '../../../schema';
22
import type { MemoryDetail } from '../../aws/agentcore-control';
3-
import { getMemoryDetail, listMemories } from '../../aws/agentcore-control';
3+
import { getMemoryDetail, listAllMemories } from '../../aws/agentcore-control';
44
import { LocalCdkProject } from '../../cdk/local-cdk-project';
55
import { silentIoHost } from '../../cdk/toolkit-lib';
66
import { ExecLogger } from '../../logging';
@@ -106,9 +106,9 @@ export async function handleImportMemory(options: ImportResourceOptions): Promis
106106
} else {
107107
// List memories and show to user
108108
onProgress('Listing memories in your account...');
109-
const listResult = await listMemories({ region: target.region, maxResults: 100 });
109+
const memories = await listAllMemories({ region: target.region });
110110

111-
if (listResult.memories.length === 0) {
111+
if (memories.length === 0) {
112112
const error = 'No memories found in your account.';
113113
logger.endStep('error', error);
114114
logger.finalize(false);
@@ -121,18 +121,30 @@ export async function handleImportMemory(options: ImportResourceOptions): Promis
121121
};
122122
}
123123

124-
// Display list
125-
console.log(`\nFound ${listResult.memories.length} memory(ies):\n`);
126-
for (let i = 0; i < listResult.memories.length; i++) {
127-
const m = listResult.memories[i]!;
128-
console.log(` ${dim}[${i + 1}]${reset} ${m.memoryId}${m.status}`);
129-
}
130-
console.log('');
124+
if (memories.length === 1) {
125+
// Auto-select the only memory
126+
memoryId = memories[0]!.memoryId;
127+
onProgress(`Found 1 memory: ${memoryId}. Auto-selecting.`);
128+
} else {
129+
// Display list
130+
console.log(`\nFound ${memories.length} memory(ies):\n`);
131+
for (let i = 0; i < memories.length; i++) {
132+
const m = memories[i]!;
133+
console.log(` ${dim}[${i + 1}]${reset} ${m.memoryId}${m.status}`);
134+
}
135+
console.log('');
131136

132-
const error = 'Multiple memories found. Use --arn <memoryArn> to specify which memory to import.';
133-
logger.endStep('error', error);
134-
logger.finalize(false);
135-
return { success: false, error, resourceType: 'memory', resourceName: '', logPath: logger.getRelativeLogPath() };
137+
const error = 'Multiple memories found. Use --arn <memoryArn> to specify which memory to import.';
138+
logger.endStep('error', error);
139+
logger.finalize(false);
140+
return {
141+
success: false,
142+
error,
143+
resourceType: 'memory',
144+
resourceName: '',
145+
logPath: logger.getRelativeLogPath(),
146+
};
147+
}
136148
}
137149

138150
onProgress(`Fetching memory details for ${memoryId}...`);

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

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AgentEnvSpec } from '../../../schema';
22
import type { AgentRuntimeDetail } from '../../aws/agentcore-control';
3-
import { getAgentRuntimeDetail, listAgentRuntimes } from '../../aws/agentcore-control';
3+
import { getAgentRuntimeDetail, listAllAgentRuntimes } from '../../aws/agentcore-control';
44
import { LocalCdkProject } from '../../cdk/local-cdk-project';
55
import { silentIoHost } from '../../cdk/toolkit-lib';
66
import { ExecLogger } from '../../logging';
@@ -75,6 +75,22 @@ function toAgentEnvSpec(
7575
spec.authorizerConfiguration = runtime.authorizerConfiguration as AgentEnvSpec['authorizerConfiguration'];
7676
}
7777

78+
if (runtime.environmentVariables && Object.keys(runtime.environmentVariables).length > 0) {
79+
spec.envVars = Object.entries(runtime.environmentVariables).map(([name, value]) => ({ name, value }));
80+
}
81+
82+
if (runtime.tags && Object.keys(runtime.tags).length > 0) {
83+
spec.tags = runtime.tags;
84+
}
85+
86+
if (runtime.lifecycleConfiguration) {
87+
spec.lifecycleConfiguration = runtime.lifecycleConfiguration;
88+
}
89+
90+
if (runtime.requestHeaderAllowlist && runtime.requestHeaderAllowlist.length > 0) {
91+
spec.requestHeaderAllowlist = runtime.requestHeaderAllowlist;
92+
}
93+
7894
return spec;
7995
}
8096

@@ -112,9 +128,9 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
112128
} else {
113129
// List runtimes and let user pick
114130
onProgress('Listing runtimes in your account...');
115-
const listResult = await listAgentRuntimes({ region: target.region, maxResults: 100 });
131+
const runtimes = await listAllAgentRuntimes({ region: target.region });
116132

117-
if (listResult.runtimes.length === 0) {
133+
if (runtimes.length === 0) {
118134
const error = 'No runtimes found in your account. Deploy a runtime first.';
119135
logger.endStep('error', error);
120136
logger.finalize(false);
@@ -127,19 +143,31 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
127143
};
128144
}
129145

130-
// Display list for user to pick
131-
console.log(`\nFound ${listResult.runtimes.length} runtime(s):\n`);
132-
for (let i = 0; i < listResult.runtimes.length; i++) {
133-
const r = listResult.runtimes[i]!;
134-
console.log(` ${dim}[${i + 1}]${reset} ${r.agentRuntimeName} (${r.agentRuntimeId}) — ${r.status}`);
135-
}
136-
console.log('');
146+
if (runtimes.length === 1) {
147+
// Auto-select the only runtime
148+
runtimeId = runtimes[0]!.agentRuntimeId;
149+
onProgress(`Found 1 runtime: ${runtimes[0]!.agentRuntimeName} (${runtimeId}). Auto-selecting.`);
150+
} else {
151+
// Display list for user to pick
152+
console.log(`\nFound ${runtimes.length} runtime(s):\n`);
153+
for (let i = 0; i < runtimes.length; i++) {
154+
const r = runtimes[i]!;
155+
console.log(` ${dim}[${i + 1}]${reset} ${r.agentRuntimeName} (${r.agentRuntimeId}) — ${r.status}`);
156+
}
157+
console.log('');
137158

138-
// For non-interactive mode, require --arn
139-
const error = 'Multiple runtimes found. Use --arn <runtimeArn> to specify which runtime to import.';
140-
logger.endStep('error', error);
141-
logger.finalize(false);
142-
return { success: false, error, resourceType: 'runtime', resourceName: '', logPath: logger.getRelativeLogPath() };
159+
// For non-interactive mode, require --arn
160+
const error = 'Multiple runtimes found. Use --arn <runtimeArn> to specify which runtime to import.';
161+
logger.endStep('error', error);
162+
logger.finalize(false);
163+
return {
164+
success: false,
165+
error,
166+
resourceType: 'runtime',
167+
resourceName: '',
168+
logPath: logger.getRelativeLogPath(),
169+
};
170+
}
143171
}
144172

145173
onProgress(`Fetching runtime details for ${runtimeId}...`);

0 commit comments

Comments
 (0)