Add command result support for resource commands#15622
Add command result support for resource commands#15622
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15622Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15622" |
|
Something I've been playing around with in my JWT scenario is writing the output directly to the user's clipboard - something worth thinking about. Works reasonably well for secrets |
d532f96 to
2f9c523
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2f9c523 to
fc149ed
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds end-to-end support for resource commands to return structured result data (text/JSON) back to callers (Dashboard, CLI, MCP), flowing from hosting model → backchannel/gRPC → UI/CLI.
Changes:
- Introduces
CommandResultFormatand extendsExecuteCommandResult/CommandResults.Success(...)to carry result payload + format. - Extends dashboard gRPC/backchannel contracts and mapping to transport
result/result_format. - Updates Dashboard/CLI/MCP to surface command results, plus adds/updates tests and regenerated polyglot codegen snapshots.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/ResourceCommandServiceTests.cs | Adds hosting tests for result propagation and replica aggregation behavior. |
| tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts | Updates TS generated API snapshots for new fields/enum. |
| tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs | Updates Rust generated API snapshots for new fields/enum. |
| tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py | Updates Python generated API snapshots for new fields/enum. |
| tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java | Updates Java generated API snapshots for new fields/enum. |
| tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go | Updates Go generated API snapshots for new fields/enum. |
| tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs | Adds a hook to capture DisplayMessage calls in CLI tests. |
| tests/Aspire.Cli.Tests/Mcp/ExecuteResourceCommandToolTests.cs | Verifies MCP tool returns additional content block when a command returns result data. |
| tests/Aspire.Cli.Tests/Commands/ResourceCommandHelperTests.cs | Adds CLI tests around displaying command results. |
| src/Aspire.Hosting/Dashboard/proto/dashboard_service.proto | Adds result and result_format fields + CommandResultFormat enum to the dashboard proto. |
| src/Aspire.Hosting/Dashboard/DashboardServiceData.cs | Extends command execution tuple to include result payload + format. |
| src/Aspire.Hosting/Dashboard/DashboardService.cs | Maps hosting command results into gRPC response fields. |
| src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs | Adds backchannel DTO properties for result payload + format. |
| src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs | Maps hosting ExecuteCommandResult to backchannel result fields. |
| src/Aspire.Hosting/ApplicationModel/ResourceCommandService.cs | Aggregates replica results and returns the first successful result payload/format. |
| src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs | Adds CommandResultFormat, ExecuteCommandResult.Result/ResultFormat, and CommandResults.Success(...) overload. |
| src/Aspire.Dashboard/ServiceClient/Partials.cs | Maps gRPC result fields into dashboard view model. |
| src/Aspire.Dashboard/Model/ResourceCommandResponseViewModel.cs | Adds result payload + format to the dashboard model and defines CommandResultFormat. |
| src/Aspire.Dashboard/Model/DashboardCommandExecutor.cs | Opens a text visualizer dialog on successful command results (locks to JSON when applicable). |
| src/Aspire.Cli/Mcp/Tools/ExecuteResourceCommandTool.cs | Returns command result as an additional MCP TextContentBlock on success. |
| src/Aspire.Cli/Commands/ResourceCommandHelper.cs | Displays command result in CLI after successful execution. |
| playground/Stress/Stress.AppHost/Program.cs | Adds sample commands returning JSON/text results in the stress playground. |
| _ = TextVisualizerDialog.OpenDialogAsync(new OpenTextVisualizerDialogOptions | ||
| { | ||
| DialogService = serviceProvider.GetRequiredService<DashboardDialogService>(), | ||
| ValueDescription = command.GetDisplayName(), | ||
| Value = response.Result, | ||
| FixedFormat = fixedFormat | ||
| }); |
There was a problem hiding this comment.
TextVisualizerDialog.OpenDialogAsync(...) is an async call that’s currently fire-and-forget (_ = ...) without any exception observation. If ShowDialogAsync throws, the exception can become unobserved and the user won’t see the result dialog. Consider awaiting this call (it should complete once the dialog is shown, not when it’s closed), or explicitly handle/log exceptions if you need it to be non-blocking.
|
|
||
| if (response.Result is not null) | ||
| { | ||
| interactionService.DisplayMessage(KnownEmojis.Information, response.Result.EscapeMarkup()); |
There was a problem hiding this comment.
IInteractionService.DisplayMessage already escapes Spectre markup when allowMarkup is false (see ConsoleInteractionService.DisplayMessage). Calling EscapeMarkup() here will double-escape the result, producing incorrect output (e.g., [[[[/]]]]). Pass the raw response.Result and rely on DisplayMessage’s built-in escaping (or set allowMarkup: true and escape exactly once if you intend to pre-escape).
| interactionService.DisplayMessage(KnownEmojis.Information, response.Result.EscapeMarkup()); | |
| interactionService.DisplayMessage(KnownEmojis.Information, response.Result); |
| Assert.Equal(0, exitCode); | ||
| Assert.NotNull(capturedMessage); | ||
| // Verify the brackets are escaped so Spectre doesn't interpret them as markup | ||
| // EscapeMarkup doubles [ and ] so [" becomes [[" | ||
| Assert.Contains("[[", capturedMessage); | ||
| Assert.Contains("]]", capturedMessage); | ||
| } |
There was a problem hiding this comment.
This test asserts that ResourceCommandHelper escapes Spectre markup (expects [[/]]), but in production the escaping is performed by ConsoleInteractionService.DisplayMessage when allowMarkup is false. With the current helper implementation, real output will be double-escaped; if the helper is fixed to stop pre-escaping, this test should be updated to reflect the actual contract (helper passes raw result; interaction service handles escaping), or TestInteractionService.DisplayMessage should mimic the production escaping behavior to catch double-escaping regressions.
|
|
||
| if (response.Result is not null) | ||
| { | ||
| interactionService.DisplayMessage(KnownEmojis.Information, response.Result.EscapeMarkup()); |
There was a problem hiding this comment.
I think the method used here should depend on the content result format. JSON (and maybe text as well) should use IInteractionService.DisplayRawText.
And we should consider making the aspire execute resource command in the CLI sending all messages to stderr and only printing the command result to stdout.
| message ResourceCommandResponse { | ||
| ResourceCommandResponseKind kind = 1; | ||
| optional string error_message = 2; | ||
| optional string result = 3; |
There was a problem hiding this comment.
This allows output to be printed once when the command is finished. What do you think of being able to support streaming result content?
It would be a lot more complicated. We'd need to introduce a new gRPC method for calling resource commands, and it would support streaming result messages. ResourceCommandService would need to be updated to support it. Dashboard would try to use the new method, and if it's not implemented then use the old one. Some other stuff I haven't thought about.
|
🎬 CLI E2E Test Recordings — 52 recordings uploaded (commit View recordings
📹 Recordings uploaded automatically from CI run #23626358412 |
Summary
Adds
ResultandResultFormatproperties toExecuteCommandResult, allowing resource commands to return structured output data (text or JSON) back to the caller.Part of #15610
Changes
Model
CommandResultFormatenum:None,Text,JsonExecuteCommandResultgainsResult(string?) andResultFormatpropertiesCommandResults.Success(result, format)overload for producing resultsProto/gRPC
ResourceCommandResponsegetsoptional string resultandCommandResultFormat result_formatfieldsBackchannel
ExecuteResourceCommandResponsegainsResultandResultFormat(string) propertiesAuxiliaryBackchannelRpcTargetDashboard
ResourceCommandResponseViewModelgainsResultandCommandResultFormatTextVisualizerDialog(existing JSON/XML/text viewer with syntax highlighting)FixedFormatto lock the viewer to JSON modeCLI
[/]in JSON from being interpreted as markup)TextContentBlockReplica aggregation
Testing