Skip to content

feat(runtime): add Runtime invoke command - #1820

Open
aidandaly24 wants to merge 6 commits into
refactorfrom
feat/runtime-invoke-lean
Open

feat(runtime): add Runtime invoke command#1820
aidandaly24 wants to merge 6 commits into
refactorfrom
feat/runtime-invoke-lean

Conversation

@aidandaly24

@aidandaly24 aidandaly24 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description

Adds headless invocation for AgentCore Runtimes through agentcore runtime invoke.

IAM requests use the AgentCore data-plane SDK. CUSTOM_JWT requests use the generated Runtime data endpoint with an explicit bearer token. Payloads and bearer tokens can come from inline values, files, or stdin, with validation preventing both from consuming stdin at once.

The command maps the public Runtime invocation contract, including endpoint qualifiers, content negotiation, Runtime and MCP session IDs, ordered application headers, MCP metadata, and tracing headers. Application headers are validated against the Runtime allowlist and cannot override reserved protocol headers.

Raw output streams exact response bytes to stdout and writes response metadata to stderr. Binary responses can be written directly to a file, while --json emits one buffered response envelope with UTF-8 or base64 body encoding. Partial responses retain bytes already received and report whether the stream completed.

SIGINT propagates cancellation through Runtime lookup, transport, and response output. Usage failures return exit code 2, interruptions return 130, and transport or stream failures avoid exposing payloads, tokens, headers, or arbitrary upstream error causes. Invoke payloads, application headers, and bearer tokens are marked sensitive for command logging.

This change also adds shared abortable-stream handling used by Runtime and Harness, extends the Core Runtime client with IAM and CUSTOM_JWT invocation transport, and documents IAM, CUSTOM_JWT, MCP, binary, and JSON workflows.

The interactive Runtime invoke console has moved to the stacked feat/runtime-invoke-tui branch and is not part of this PR. Runtime creation and mutation, deployment or project resolution, cross-account invocation, Runtime ARNs, version targeting, custom request paths, and the Runtime /commands route also remain outside this change.

Related Issue

N/A

Documentation PR

N/A. README command, invocation, output, and MCP documentation is included in this PR.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (please describe):

Testing

Verified on current PR head 7a3520d8:

  • bun test (422 passed, 0 failed)
  • bun run typecheck
  • bun run lint:check
  • bun run format:check
  • bun run build
  • bun audit
  • agentcore runtime invoke --help
  • Missing --payload exits with usage status 2
  • GitHub builds and unit tests on Linux, Windows, and macOS
  • Live IAM invocation against a newly deployed public HTTP Runtime streamed incremental SSE chunks, reported complete=true, and exited with status 0

Checklist

  • I have read the CONTRIBUTING document
  • I have added necessary tests that prove the feature works
  • I have updated the documentation accordingly
  • I have added appropriate examples to the documentation
  • My changes generate no new warnings
  • No dependent changes are required

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@github-actions github-actions Bot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Jul 23, 2026
@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.84301% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 95.28%. Comparing base (0a43f48) to head (2b8ad05).

Files with missing lines Patch % Lines
src/handlers/runtime/invoke/index.tsx 98.94% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           refactor    #1820      +/-   ##
============================================
+ Coverage     94.91%   95.28%   +0.37%     
============================================
  Files           154      159       +5     
  Lines          7455     8062     +607     
============================================
+ Hits           7076     7682     +606     
- Misses          379      380       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Jul 23, 2026
breadcrumb: string[];
description?: string;
onSelect: (qualifier: string) => void;
onEscape?: () => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to allow this to be overridden? My understanding is the esc consistently means go back to the last page right now in the tui, and by allowing an override we open the door to potentially unexpected behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I agree callers should not override the Escape key itself. Would you support replacing this with a required semantic onBack callback?

interface RuntimeEndpointPickerProps extends ScreenProps {
  // ...
  onBack: () => void;
}

PaginatedTablePicker would remain the only component binding Escape, and Escape would always invoke onBack. Route-backed screens would navigate to their explicit parent, while embedded target pickers would close their local picker state.

I'd apply this consistently to RuntimePicker, RuntimeEndpointPicker, and HarnessEndpointPicker. I don't think always using navigate(-1) is sufficient because direct TUI deep links and embedded pickers may not have a preceding history entry.

Comment thread src/core/runtime.tsx
Comment thread src/core/runtime.tsx
throw new TypeError("CUSTOM_JWT requires an HTTPS endpoint");
}
url.pathname = `${url.pathname.replace(/\/?$/, "/")}runtimes/${encodeURIComponent(runtimeId)}/invocations`;
url.search = new URLSearchParams({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it makes sense to expand the responsibility of CoreFetch to be a generic HttpClient where we can move some of this logic? I imagine we'll hit other cases where we want to do something similar to here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be a good use case for CoreFetch. I agree that this logic could be moved over I'll scope that out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I scoped this out, and most of the surrounding behavior appears Runtime-specific; CoreFetch itself is already the generic injectable transport. Were you thinking the shared client would own only fetch/error handling, or also the Runtime URL, headers, and response mapping?

I'm hesitant to move the latter into a generic client, but I'm open to extracting the CUSTOM_JWT path into a Runtime-specific transport if the goal is to simplify RuntimeClient.

I'd also be happy to introduce a generic HTTP client once there is another consumer of the same behavior. Gateway invoke may become one, for example. Having that second implementation would give us a concrete shared contract rather than guessing now about which authentication, request, and error-handling policies are actually common.

Comment thread src/handlers/runtime/invoke/index.tsx Outdated
try {
await renderTuiAt(path, ctx, core, io);
} catch (error) {
throw error instanceof TypeError &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it worth defining an explicit error for this case? wondering if depending on the message might be brittle.

Comment thread src/handlers/runtime/invoke/index.tsx Outdated
if (jsonOutput && flags["output-file"] !== undefined) {
throw new UsageError("--json cannot be used with --output-file");
}
if (flags["output-file"] === "") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be enforced in the schema?

Comment thread src/handlers/runtime/invoke/request.ts Outdated
@@ -0,0 +1,165 @@
import { readFile } from "node:fs/promises";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this request/response live outside of handlers? I kind of thought the original idea is that handlers are lightweight and mirror the command structure, with functionality mostly coming from core or injected through context.

@AlexanderRichey curious to your thoughts here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been treating the handler directory as the command-owned application layer, rather than limiting it to the route entrypoint itself. request.ts handles CLI sources, validation, and defaults, while response.ts handles stdout/stderr, files, TTY behavior, and JSON presentation. RuntimeClient owns transport.

This matches Harness, where request/response transformation lives under handlers/harness/invoke, while core/harness.tsx only owns SDK transport and abort wrapping.

Moving these concerns into Core would make Core aware of CLI input and output policy. Were you thinking they should move into Core, or into a separate command-independent Runtime module outside handlers? normalizeRuntimeInvokeRequest is the one boundary I could see revisiting, but the other helpers appear command-specific.

Comment thread src/handlers/runtime/invoke/request.ts Outdated
try {
validateHeaderValue(name, value);
} catch {
throw new UsageError(`Invalid header value for ${name}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way we can give a hint to the user about why its invalid? Also, I think wiring up logging here could be valuable as well.

Comment thread src/runnable/index.tsx Outdated
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
console.error(`${error.name}: ${error.message}`);
} catch (error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler to define our own error base class that carries the exit code directly, and then leverage it here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The shared AgentCoreCLIError introduced by #1834 now provides this. This PR was rebased and migrated onto it in 2b8ad055.

Comment thread src/runnable/index.tsx Outdated
return ExitCode.SUCCESS;
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
console.error(`${error.name}: ${error.message}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to keep this console.error, it ensures that any initialization errors are logged to the user.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. Unreported failures still reach console.error; only Commander output and response failures that were already reported are suppressed.

Comment thread src/router/router.test.ts Outdated
});

test("reports invalid input via command.error (throws under exitOverride)", async () => {
test("a short alias preserves encounter order for repeated variadic values", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what exactly are we verifying here?

@AlexanderRichey AlexanderRichey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't have time to read the whole thing, but left some initial feedback in the comments.

Comment thread src/runnable/index.tsx
const error = e instanceof Error ? e : new Error(String(e));
console.error(`${error.name}: ${error.message}`);
} catch (error) {
if (error instanceof CommanderError) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the need for something that controls the exit code, but I'm not sure this is how we ought to do it. I think we should define something like AgentCoreError that is a custom Error type that has an optional exitCode field. This should probably come in a separate PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I’ll keep this PR scoped to structural exitCode handling for the local Runtime and router usage errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: #1834 has merged, so after rebasing this PR now uses AgentCoreCLIError directly. Runtime and router validation errors use InputValidationError with exit code 2, and runWithExitCode reads exit codes from the shared base class. Addressed in 2b8ad055.

Comment thread src/router/handler.tsx Outdated
// Flag<string, unknown>.
export interface Flag<N extends string = string, T = unknown> {
name: N;
short?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should hold off on adding this feature for now. This will end up adding a lot more for us to maintain.

Comment thread src/handlers/runtime/types.tsx Outdated
} from "@aws-sdk/client-bedrock-agentcore-control";
import type { CoreOptions } from "../../core/types";

export interface RuntimeInvokeRequest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be types and not interfaces. Types hold data whereas interfaces are abstract, don't hold data themselves, and have to be implemented by something else.

Comment thread src/handlers/runtime/invoke/screen.tsx Outdated
}
responseText += decoder.decode();
updateExchange({ response: responseText });
let text: string | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a whole lot going on here. I tend to think there's a way to simplify all this. Let's find some time next week to discuss.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Since this review, 9ccab9b aligned the Runtime console more closely with Harness: payload editing moved into RuntimePayloadInput, and scrolling, key hints, status presentation, and pre-stream error handling now follow the Harness interaction model. Advanced options are already isolated in RequestOptionsScreen. The console still owns several responsibilities, and I agree there may be a cleaner split. Let’s discuss the intended boundary next week, and I’ll make a focused change from there.

@aidandaly24
aidandaly24 force-pushed the feat/runtime-invoke-lean branch 3 times, most recently from b762a8a to e6d2e49 Compare July 27, 2026 18:33
@aidandaly24 aidandaly24 changed the title feat(runtime): add Runtime invocation feat(runtime): add Runtime invoke command Jul 27, 2026
@aidandaly24
aidandaly24 force-pushed the feat/runtime-invoke-lean branch from 7a3520d to 2b8ad05 Compare July 28, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants