Skip to content

Commit 14da3f9

Browse files
authored
feat: add invoke APIs for agent inspector (#1326)
feat: add invoke APIs for agent inspector (#1326) Checks for `target=deployed` query param on invocation apis from agent inspector to wire requests through to deployed runtime instead of locally run agents
1 parent 5a0fb0b commit 14da3f9

11 files changed

Lines changed: 1711 additions & 142 deletions

File tree

src/cli/commands/invoke/__tests__/resolve.test.ts

Lines changed: 479 additions & 0 deletions
Large diffs are not rendered by default.

src/cli/commands/invoke/action.ts

Lines changed: 54 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,8 @@ import { ANSI } from '../../constants';
1616
import { isPreviewEnabled } from '../../feature-flags';
1717
import { InvokeLogger } from '../../logging';
1818
import { formatMcpToolList } from '../../operations/dev/utils';
19-
import {
20-
canFetchHarnessToken,
21-
canFetchRuntimeToken,
22-
fetchHarnessToken,
23-
fetchRuntimeToken,
24-
} from '../../operations/fetch-access';
25-
import { generateSessionId } from '../../operations/session';
19+
import { canFetchHarnessToken, fetchHarnessToken } from '../../operations/fetch-access';
20+
import { resolveInvokeTarget } from './resolve';
2621
import type { InvokeOptions, InvokeResult } from './types';
2722
import { randomUUID } from 'node:crypto';
2823

@@ -49,43 +44,37 @@ export async function loadInvokeConfig(configIO: ConfigIO = new ConfigIO()): Pro
4944
export async function handleInvoke(context: InvokeContext, options: InvokeOptions = {}): Promise<InvokeResult> {
5045
const { project, deployedState, awsTargets } = context;
5146

52-
// Resolve target
53-
const targetNames = Object.keys(deployedState.targets);
54-
if (targetNames.length === 0) {
55-
return {
56-
success: false,
57-
error: new ResourceNotFoundError('No deployed targets found. Run `agentcore deploy` first.'),
58-
};
59-
}
60-
61-
const selectedTargetName = options.targetName ?? targetNames[0]!;
62-
63-
if (options.targetName && !targetNames.includes(options.targetName)) {
64-
return {
65-
success: false,
66-
error: new ResourceNotFoundError(
67-
`Target '${options.targetName}' not found. Available: ${targetNames.join(', ')}`
68-
),
69-
};
70-
}
71-
72-
const targetState = deployedState.targets[selectedTargetName];
73-
const targetConfig = awsTargets.find(t => t.name === selectedTargetName);
74-
75-
if (!targetConfig) {
76-
return {
77-
success: false,
78-
error: new ResourceNotFoundError(`Target config '${selectedTargetName}' not found in aws-targets`),
79-
};
80-
}
81-
82-
// Preview: route to harness or runtime
47+
// Preview: route to harness before runtime resolution
8348
if (isPreviewEnabled()) {
8449
const harnessEntries = project.harnesses ?? [];
8550
const isHarnessInvoke = options.harnessName != null || (harnessEntries.length > 0 && project.runtimes.length === 0);
8651

8752
if (isHarnessInvoke) {
88-
return handleHarnessInvoke(project, targetState, targetConfig, selectedTargetName, options);
53+
const targetNames = Object.keys(deployedState.targets);
54+
if (targetNames.length === 0) {
55+
return {
56+
success: false,
57+
error: new ResourceNotFoundError('No deployed targets found. Run `agentcore deploy` first.'),
58+
};
59+
}
60+
const selectedTarget = options.targetName ?? targetNames[0]!;
61+
if (options.targetName && !targetNames.includes(options.targetName)) {
62+
return {
63+
success: false,
64+
error: new ResourceNotFoundError(
65+
`Target '${options.targetName}' not found. Available: ${targetNames.join(', ')}`
66+
),
67+
};
68+
}
69+
const targetState = deployedState.targets[selectedTarget];
70+
const targetConfig = awsTargets.find(t => t.name === selectedTarget);
71+
if (!targetConfig) {
72+
return {
73+
success: false,
74+
error: new ResourceNotFoundError(`Target config '${selectedTarget}' not found in aws-targets`),
75+
};
76+
}
77+
return handleHarnessInvoke(project, targetState, targetConfig, selectedTarget, options);
8978
}
9079

9180
if (harnessEntries.length > 0 && project.runtimes.length > 0 && !options.agentName) {
@@ -102,32 +91,26 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
10291
}
10392
}
10493

105-
if (project.runtimes.length === 0) {
106-
return { success: false, error: new ValidationError('No agents defined in configuration') };
107-
}
108-
109-
// Resolve agent
110-
const agentNames = project.runtimes.map(a => a.name);
111-
112-
if (!options.agentName && project.runtimes.length > 1) {
113-
return {
114-
success: false,
115-
error: new ValidationError(`Multiple runtimes found. Use --runtime to specify one: ${agentNames.join(', ')}`),
116-
};
117-
}
118-
119-
const agentSpec = options.agentName ? project.runtimes.find(a => a.name === options.agentName) : project.runtimes[0];
94+
const resolved = await resolveInvokeTarget({
95+
project,
96+
deployedState,
97+
awsTargets,
98+
agentName: options.agentName,
99+
targetName: options.targetName,
100+
bearerToken: options.bearerToken,
101+
sessionId: options.sessionId,
102+
});
120103

121-
if (options.agentName && !agentSpec) {
122-
return {
123-
success: false,
124-
error: new ResourceNotFoundError(`Agent '${options.agentName}' not found. Available: ${agentNames.join(', ')}`),
125-
};
104+
if (!resolved.success) {
105+
return { success: false, error: resolved.error };
126106
}
127107

128-
if (!agentSpec) {
129-
return { success: false, error: new ValidationError('No agents defined in configuration') };
130-
}
108+
const { agentSpec, targetName: selectedTargetName, targetConfig, runtimeArn, baggage } = resolved;
109+
options = {
110+
...options,
111+
bearerToken: resolved.bearerToken ?? options.bearerToken,
112+
sessionId: resolved.sessionId ?? options.sessionId,
113+
};
131114

132115
// Warn about VPC mode endpoint requirements
133116
if (agentSpec.networkMode === 'VPC') {
@@ -136,69 +119,11 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
136119
);
137120
}
138121

139-
// Get the deployed state for this specific agent
140-
const agentState = targetState?.resources?.runtimes?.[agentSpec.name];
141-
142-
if (!agentState) {
143-
return {
144-
success: false,
145-
error: new ValidationError(`Agent '${agentSpec.name}' is not deployed to target '${selectedTargetName}'`),
146-
};
147-
}
148-
149-
// Build config bundle baggage if a bundle is associated with this agent
150-
const deployedBundles = targetState?.resources?.configBundles ?? {};
151-
let baggage: string | undefined;
152-
const bundleSpec = project.configBundles?.find(b => {
153-
const keys = Object.keys(b.components ?? {});
154-
return keys.some(k => k === `{{runtime:${agentSpec.name}}}`);
155-
});
156-
if (bundleSpec) {
157-
const bundleState = deployedBundles[bundleSpec.name];
158-
if (bundleState?.bundleArn && bundleState?.versionId) {
159-
baggage = `aws.agentcore.configbundle_arn=${encodeURIComponent(bundleState.bundleArn)},aws.agentcore.configbundle_version=${encodeURIComponent(bundleState.versionId)}`;
160-
}
161-
}
162-
163-
// Auto-fetch bearer token for CUSTOM_JWT agents when not provided
164-
if (agentSpec.authorizerType === 'CUSTOM_JWT' && !options.bearerToken) {
165-
const canFetch = await canFetchRuntimeToken(agentSpec.name);
166-
if (canFetch) {
167-
try {
168-
const tokenResult = await fetchRuntimeToken(agentSpec.name, { deployTarget: selectedTargetName });
169-
options = { ...options, bearerToken: tokenResult.token };
170-
} catch (err) {
171-
return {
172-
success: false,
173-
error: new ValidationError(
174-
`CUSTOM_JWT agent requires a bearer token. Auto-fetch failed: ${err instanceof Error ? err.message : String(err)}\nProvide one manually with --bearer-token.`,
175-
{ cause: err }
176-
),
177-
};
178-
}
179-
} else {
180-
return {
181-
success: false,
182-
error: new ValidationError(
183-
`Agent '${agentSpec.name}' is configured for CUSTOM_JWT but no bearer token is available.\nEither provide --bearer-token or re-add the agent with --client-id and --client-secret to enable auto-fetch.`
184-
),
185-
};
186-
}
187-
}
188-
189-
// When invoking with a bearer token (OAuth/CUSTOM_JWT), AgentCore does not
190-
// auto-generate a runtime session ID the way it does for SigV4 callers. Templates
191-
// that wire up AgentCoreMemorySessionManager require a non-null session_id, so
192-
// generate one here if the caller didn't pass --session-id.
193-
if (options.bearerToken && !options.sessionId) {
194-
options = { ...options, sessionId: generateSessionId() };
195-
}
196-
197122
// Exec mode: run shell command in runtime container
198123
if (options.exec) {
199124
const logger = new InvokeLogger({
200125
agentName: agentSpec.name,
201-
runtimeArn: agentState.runtimeArn,
126+
runtimeArn: runtimeArn,
202127
region: targetConfig.region,
203128
sessionId: options.sessionId,
204129
});
@@ -211,7 +136,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
211136
try {
212137
const result = await executeBashCommand({
213138
region: targetConfig.region,
214-
runtimeArn: agentState.runtimeArn,
139+
runtimeArn: runtimeArn,
215140
command,
216141
sessionId: options.sessionId,
217142
timeout: options.timeout,
@@ -308,7 +233,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
308233
if (agentSpec.protocol === 'MCP') {
309234
const mcpOpts = {
310235
region: targetConfig.region,
311-
runtimeArn: agentState.runtimeArn,
236+
runtimeArn: runtimeArn,
312237
userId: options.userId,
313238
headers: options.headers,
314239
bearerToken: options.bearerToken,
@@ -392,7 +317,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
392317
const a2aResult = await invokeA2ARuntime(
393318
{
394319
region: targetConfig.region,
395-
runtimeArn: agentState.runtimeArn,
320+
runtimeArn: runtimeArn,
396321
userId: options.userId,
397322
sessionId: options.sessionId,
398323
headers: options.headers,
@@ -427,7 +352,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
427352
if (agentSpec.protocol === 'AGUI') {
428353
const logger = new InvokeLogger({
429354
agentName: agentSpec.name,
430-
runtimeArn: agentState.runtimeArn,
355+
runtimeArn: runtimeArn,
431356
region: targetConfig.region,
432357
});
433358

@@ -438,7 +363,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
438363
const aguiResult = await invokeAguiRuntime(
439364
{
440365
region: targetConfig.region,
441-
runtimeArn: agentState.runtimeArn,
366+
runtimeArn: runtimeArn,
442367
sessionId: options.sessionId,
443368
userId: options.userId,
444369
logger,
@@ -496,7 +421,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
496421
// Create logger for this invocation
497422
const logger = new InvokeLogger({
498423
agentName: agentSpec.name,
499-
runtimeArn: agentState.runtimeArn,
424+
runtimeArn: runtimeArn,
500425
region: targetConfig.region,
501426
sessionId: options.sessionId,
502427
});
@@ -509,7 +434,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
509434
try {
510435
const result = await invokeAgentRuntimeStreaming({
511436
region: targetConfig.region,
512-
runtimeArn: agentState.runtimeArn,
437+
runtimeArn: runtimeArn,
513438
payload: options.prompt,
514439
sessionId: options.sessionId,
515440
userId: options.userId,
@@ -544,7 +469,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption
544469
// Non-streaming mode
545470
const response = await invokeAgentRuntime({
546471
region: targetConfig.region,
547-
runtimeArn: agentState.runtimeArn,
472+
runtimeArn: runtimeArn,
548473
payload: options.prompt,
549474
sessionId: options.sessionId,
550475
userId: options.userId,

src/cli/commands/invoke/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export { registerInvoke } from './command';
22
export { handleInvoke, loadInvokeConfig } from './action';
33
export type { InvokeContext } from './action';
4+
export { resolveInvokeTarget } from './resolve';
5+
export type { ResolveInvokeInput, ResolvedInvokeTarget, ResolveInvokeResult } from './resolve';
46
export type { InvokeResult, InvokeOptions } from './types';

0 commit comments

Comments
 (0)