Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cb839bf
feat: add runtime endpoint support to AgentCore CLI
jariy17 Apr 25, 2026
3b29773
fix: correct output key prefix for runtime endpoint parsing
jariy17 Apr 25, 2026
33b81da
fix: remove .omc state files and unused useCallback import
jariy17 Apr 27, 2026
6b4f8f3
fix: shorten runtime endpoint description to prevent TUI overflow
jariy17 Apr 27, 2026
c1ad7ea
fix: validate runtime endpoint version is a positive integer
jariy17 Apr 27, 2026
f3face8
fix: use agent/endpoint composite key to prevent React key collision
jariy17 Apr 27, 2026
21147b1
fix: render runtime endpoints in status --type runtime-endpoint
jariy17 Apr 27, 2026
132f969
fix: add runtime-endpoint to status --help --type documentation
jariy17 Apr 27, 2026
3aba3e9
fix: return richer JSON response from add runtime-endpoint
jariy17 Apr 27, 2026
2bb5a86
fix: validate endpoint version against deployed runtime version
jariy17 Apr 27, 2026
844689a
chore: remove planning and bug bash docs from PR
jariy17 Apr 27, 2026
11a7a86
fix: use composite key and parentName for endpoint identification
jariy17 Apr 27, 2026
d6389c8
test: add comprehensive unit tests for RuntimeEndpointPrimitive
jariy17 Apr 27, 2026
b9f158f
fix: remove dead findGatewayTargetReferences stub
jariy17 Apr 27, 2026
0ff1db8
fix: use BasePrimitive configIO instead of ad-hoc ConfigIO in add()
jariy17 Apr 27, 2026
be47f35
fix: use Number() instead of parseInt in TUI version validation
jariy17 Apr 27, 2026
fd8e15c
Merge branch 'main' into feat/endpoint_based_abs
jariy17 Apr 27, 2026
ddbeff2
chore: fix prettier formatting
jariy17 Apr 27, 2026
117a07e
fix: use T[] instead of Array<T> to satisfy eslint array-type rule
jariy17 Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ ProtocolTesting/

# Auto-cloned CDK constructs (from scripts/bundle.mjs)
.cdk-constructs-clone/
.omc/

# Browser tests
browser-tests/.browser-test-env
Expand Down
42 changes: 42 additions & 0 deletions src/cli/cloudformation/outputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
OnlineEvalDeployedState,
PolicyDeployedState,
PolicyEngineDeployedState,
RuntimeEndpointDeployedState,
TargetDeployedState,
} from '../../schema';
import { getCredentialProvider } from '../aws';
Expand Down Expand Up @@ -338,6 +339,40 @@ export function parsePolicyOutputs(
return policies;
}

/**
* Parse stack outputs into deployed state for runtime endpoints.
*
* Output key pattern: ApplicationAgent{AgentPascal}Endpoint{AgentPascal}{EndpointPascal}(Id|Arn)Output{Hash}
* The Agent{PascalName} prefix comes from the AgentEnvironment construct in the CDK tree.
*/
export function parseRuntimeEndpointOutputs(
outputs: StackOutputs,
endpointSpecs: { agentName: string; endpointName: string }[]
): Record<string, RuntimeEndpointDeployedState> {
const endpoints: Record<string, RuntimeEndpointDeployedState> = {};
const outputKeys = Object.keys(outputs);

for (const { agentName, endpointName } of endpointSpecs) {
const agentPascal = toPascalId(agentName);
const endpointPascal = toPascalId('Endpoint', agentName, endpointName);
const idPrefix = `ApplicationAgent${agentPascal}${endpointPascal}IdOutput`;
const arnPrefix = `ApplicationAgent${agentPascal}${endpointPascal}ArnOutput`;

const idKey = outputKeys.find(k => k.startsWith(idPrefix));
const arnKey = outputKeys.find(k => k.startsWith(arnPrefix));

if (idKey && arnKey) {
const key = `${agentName}/${endpointName}`;
endpoints[key] = {
endpointId: outputs[idKey]!,
endpointArn: outputs[arnKey]!,
};
}
}

return endpoints;
}

export interface BuildDeployedStateOptions {
targetName: string;
stackName: string;
Expand All @@ -351,6 +386,7 @@ export interface BuildDeployedStateOptions {
onlineEvalConfigs?: Record<string, OnlineEvalDeployedState>;
policyEngines?: Record<string, PolicyEngineDeployedState>;
policies?: Record<string, PolicyDeployedState>;
runtimeEndpoints?: Record<string, RuntimeEndpointDeployedState>;
}

/**
Expand All @@ -370,6 +406,7 @@ export function buildDeployedState(opts: BuildDeployedStateOptions): DeployedSta
onlineEvalConfigs,
policyEngines,
policies,
runtimeEndpoints,
} = opts;
const targetState: TargetDeployedState = {
resources: {
Expand Down Expand Up @@ -404,6 +441,11 @@ export function buildDeployedState(opts: BuildDeployedStateOptions): DeployedSta
targetState.resources!.onlineEvalConfigs = onlineEvalConfigs;
}

// Add runtime endpoint state if endpoints exist
if (runtimeEndpoints && Object.keys(runtimeEndpoints).length > 0) {
targetState.resources!.runtimeEndpoints = runtimeEndpoints;
}

return {
targets: {
...existingState?.targets,
Expand Down
13 changes: 13 additions & 0 deletions src/cli/commands/deploy/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
parseOnlineEvalOutputs,
parsePolicyEngineOutputs,
parsePolicyOutputs,
parseRuntimeEndpointOutputs,
} from '../../cloudformation';
import { getErrorMessage } from '../../errors';
import { ExecLogger } from '../../logging';
Expand Down Expand Up @@ -403,6 +404,17 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
);
const policies = parsePolicyOutputs(outputs, policySpecs);

// Parse runtime endpoint outputs
const endpointSpecs: { agentName: string; endpointName: string }[] = [];
for (const runtime of context.projectSpec.runtimes) {
if (runtime.endpoints) {
for (const endpointName of Object.keys(runtime.endpoints)) {
endpointSpecs.push({ agentName: runtime.name, endpointName });
}
}
}
const runtimeEndpoints = parseRuntimeEndpointOutputs(outputs, endpointSpecs);

// Parse gateway outputs
const gatewaySpecs =
mcpSpec?.agentCoreGateways?.reduce(
Expand All @@ -428,6 +440,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
onlineEvalConfigs,
policyEngines,
policies,
runtimeEndpoints,
});
await configIO.writeDeployedState(deployedState);

Expand Down
1 change: 1 addition & 0 deletions src/cli/commands/remove/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type ResourceType =
| 'agent'
| 'gateway'
| 'gateway-target'
| 'runtime-endpoint'
| 'memory'
| 'credential'
| 'evaluator'
Expand Down
36 changes: 35 additions & 1 deletion src/cli/commands/status/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ export interface ResourceStatusEntry {
| 'evaluator'
| 'online-eval'
| 'policy-engine'
| 'policy';
| 'policy'
| 'runtime-endpoint';
name: string;
deploymentState: ResourceDeploymentState;
identifier?: string;
detail?: string;
parentName?: string;
error?: string;
invocationUrl?: string;
}
Expand Down Expand Up @@ -79,13 +81,15 @@ function diffResourceSet<TLocal extends { name: string }, TDeployed>({
getIdentifier,
getLocalDetail,
getDeployedKey,
getParentName,
}: {
resourceType: ResourceStatusEntry['resourceType'];
localItems: TLocal[];
deployedRecord: Record<string, TDeployed>;
getIdentifier: (deployed: TDeployed) => string | undefined;
getLocalDetail?: (item: TLocal) => string | undefined;
getDeployedKey?: (item: TLocal) => string;
getParentName?: (item: TLocal) => string | undefined;
}): ResourceStatusEntry[] {
const entries: ResourceStatusEntry[] = [];
const localKeys = new Set(localItems.map(item => (getDeployedKey ? getDeployedKey(item) : item.name)));
Expand All @@ -99,16 +103,20 @@ function diffResourceSet<TLocal extends { name: string }, TDeployed>({
deploymentState: deployed ? 'deployed' : 'local-only',
identifier: deployed ? getIdentifier(deployed) : undefined,
detail: getLocalDetail?.(item),
parentName: getParentName?.(item),
});
}

for (const [name, deployed] of Object.entries(deployedRecord)) {
if (!localKeys.has(name)) {
// For pending-removal entries, try to extract parentName from composite key
const slashIdx = name.indexOf('/');
entries.push({
resourceType,
name,
deploymentState: 'pending-removal',
identifier: getIdentifier(deployed),
parentName: getParentName && slashIdx > 0 ? name.substring(0, slashIdx) : undefined,
});
}
}
Expand Down Expand Up @@ -202,8 +210,34 @@ export function computeResourceStatuses(
getDeployedKey: item => `${item.engineName}/${item.name}`,
});

// Flatten runtime endpoints for diffing against deployed state
const localEndpoints: { name: string; agentName: string; version: number; description?: string }[] = [];
for (const runtime of project.runtimes) {
if (runtime.endpoints) {
for (const [epName, ep] of Object.entries(runtime.endpoints)) {
localEndpoints.push({
name: epName,
agentName: runtime.name,
version: ep.version,
description: ep.description,
});
}
}
}

const runtimeEndpoints = diffResourceSet({
resourceType: 'runtime-endpoint',
localItems: localEndpoints,
deployedRecord: resources?.runtimeEndpoints ?? {},
getIdentifier: deployed => deployed.endpointArn,
getLocalDetail: item => `v${item.version}${item.description ? ` — ${item.description}` : ''}`,
getDeployedKey: item => `${item.agentName}/${item.name}`,
getParentName: item => item.agentName,
});

return [
...agents,
...runtimeEndpoints,
...credentials,
...memories,
...gateways,
Expand Down
48 changes: 38 additions & 10 deletions src/cli/commands/status/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

const VALID_RESOURCE_TYPES = [
'agent',
'runtime-endpoint',
'memory',
'credential',
'gateway',
Expand Down Expand Up @@ -58,7 +59,7 @@
.option('--target <name>', 'Select deployment target')
.option(
'--type <type>',
'Filter by resource type (agent, memory, credential, gateway, evaluator, online-eval, policy-engine, policy)'
'Filter by resource type (agent, runtime-endpoint, memory, credential, gateway, evaluator, online-eval, policy-engine, policy)'
)
.option('--state <state>', 'Filter by deployment state (deployed, local-only, pending-removal)')
.option('--runtime <name>', 'Filter to a specific runtime')
Expand Down Expand Up @@ -135,6 +136,7 @@

const filtered = filterResources(result.resources, cliOptions);
const agents = filtered.filter(r => r.resourceType === 'agent');
const runtimeEndpoints = filtered.filter(r => r.resourceType === 'runtime-endpoint');
const credentials = filtered.filter(r => r.resourceType === 'credential');
const memories = filtered.filter(r => r.resourceType === 'memory');
const gateways = filtered.filter(r => r.resourceType === 'gateway');
Expand All @@ -153,15 +155,41 @@
{agents.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<Text bold>Agents</Text>
{agents.map(entry => (
<Box key={`${entry.resourceType}-${entry.name}`} flexDirection="column">
<ResourceEntry entry={entry} showRuntime />
{entry.invocationUrl && (
<Text dimColor>
{' '}URL: {entry.invocationUrl}
</Text>
)}
</Box>
{agents.map(entry => {
// Find endpoints belonging to this agent
const agentEndpoints = runtimeEndpoints.filter(ep => ep.parentName === entry.name);
return (
<Box key={`${entry.resourceType}-${entry.name}`} flexDirection="column">
<ResourceEntry entry={entry} showRuntime />
{entry.invocationUrl && (
<Text dimColor>
{' '}URL: {entry.invocationUrl}
</Text>
)}
{agentEndpoints.map(ep => (
Comment thread
notgitika marked this conversation as resolved.
Comment thread
notgitika marked this conversation as resolved.
<Text key={`${ep.parentName}/${ep.name}`}>
{' '}◉ {ep.name} <Text dimColor>{ep.detail}</Text>{' '}
<Text color={DEPLOYMENT_STATE_COLORS[ep.deploymentState] ?? 'gray'}>
[{DEPLOYMENT_STATE_LABELS[ep.deploymentState] ?? ep.deploymentState}]
</Text>
</Text>
))}
</Box>
);
})}
</Box>
)}

{agents.length === 0 && runtimeEndpoints.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<Text bold>Runtime Endpoints</Text>
{runtimeEndpoints.map(ep => (
<Text key={`${ep.parentName}/${ep.name}`}>
{' '}◉ {ep.parentName}/{ep.name} <Text dimColor>{ep.detail}</Text>{' '}
<Text color={DEPLOYMENT_STATE_COLORS[ep.deploymentState] ?? 'gray'}>
Comment thread
notgitika marked this conversation as resolved.
Comment thread
notgitika marked this conversation as resolved.
[{DEPLOYMENT_STATE_LABELS[ep.deploymentState] ?? ep.deploymentState}]
</Text>
</Text>
))}
</Box>
)}
Expand Down Expand Up @@ -239,7 +267,7 @@
});
};

function ResourceEntry({ entry, showRuntime }: { entry: ResourceStatusEntry; showRuntime?: boolean }) {

Check warning on line 270 in src/cli/commands/status/command.tsx

View workflow job for this annotation

GitHub Actions / lint

Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option
return (
<Text>
{' '}
Expand Down
1 change: 1 addition & 0 deletions src/cli/logging/remove-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface RemoveLoggerOptions {
| 'credential'
| 'gateway'
| 'gateway-target'
| 'runtime-endpoint'
| 'evaluator'
| 'online-eval'
| 'policy-engine'
Expand Down
Loading
Loading