diff --git a/Directory.Packages.props b/Directory.Packages.props
index 74e3b4e47..09aabaec6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -77,13 +77,10 @@
-
-
+
+
@@ -106,7 +103,8 @@
-
+
+
diff --git a/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/CommunityToolkit.Aspire.Hosting.Squad.ApiApp.csproj b/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/CommunityToolkit.Aspire.Hosting.Squad.ApiApp.csproj
index fca71350b..2b2d94e59 100644
--- a/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/CommunityToolkit.Aspire.Hosting.Squad.ApiApp.csproj
+++ b/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/CommunityToolkit.Aspire.Hosting.Squad.ApiApp.csproj
@@ -3,14 +3,17 @@
-
+
-
+
+
+
diff --git a/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/Program.cs b/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/Program.cs
index c783f0404..7dba6b703 100644
--- a/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/Program.cs
+++ b/examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/Program.cs
@@ -10,7 +10,7 @@
// or CliArgs boilerplate required.
//
// The single POST /ask endpoint takes a ?squad=research|dev query parameter and a
-// JSON body with a list of prompts. The coordinator decides per-prompt whether to
+// JSON body with either a single `prompt` or a list of `prompts`. The coordinator decides per-prompt whether to
// answer directly (Direct Mode in squad.agent.md), do a single agent spawn
// (Lightweight), or fan out via the task tool (Full). The "Sample prompts"
// section returned from GET / shows what each mode looks like — paste them
@@ -131,11 +131,12 @@
// Index: shows the single /ask endpoint plus a copy-paste menu of sample
// prompts that exercise each coordinator mode (Direct, Lightweight, Full).
-// Same body shape as /ask accepts — just `{"prompts": [...]}`.
+// Same body shape as /ask accepts — either `{"prompts": [...]}` or, for a
+// quick single turn, `{"prompt": "..."}`.
app.MapGet("/", () => Results.Ok(new
{
squads = squadKeysByShortName,
- endpoint = "POST /ask?squad=research|dev — body: { \"prompts\": [...] }. " +
+ endpoint = "POST /ask?squad=research|dev — body: { \"prompts\": [...] } or { \"prompt\": \"...\" }. " +
"Each prompt runs sequentially on a single AgentSession (multi-turn memory). " +
"The coordinator picks Direct / Lightweight / Full mode per prompt; only Full mode produces squad.subagent spans.",
sample_prompts = new
@@ -201,17 +202,23 @@
var agent = ResolveSquad(sp, q.Squad, out var error);
if (agent is null) return Results.BadRequest(new { error });
+ var prompts = req.GetPrompts();
+ if (prompts.Length == 0)
+ {
+ return Results.BadRequest(new { error = "Provide either 'prompt' or a non-empty 'prompts' array." });
+ }
+
using var activity = ApiAppDiagnostics.ActivitySource
.StartActivity($"squad.ask {q.Squad}", ActivityKind.Server);
activity?.SetTag("squad.name", q.Squad);
- activity?.SetTag("squad.prompt.count", req.Prompts.Length);
+ activity?.SetTag("squad.prompt.count", prompts.Length);
- logger.LogInformation("/ask squad={Squad} prompts={Count}", q.Squad, req.Prompts.Length);
+ logger.LogInformation("/ask squad={Squad} prompts={Count}", q.Squad, prompts.Length);
var turns = new List();
var session = await agent.CreateSessionAsync(ct);
- foreach (var prompt in req.Prompts)
+ foreach (var prompt in prompts)
{
var response = await agent.RunAsync(prompt, session, cancellationToken: ct);
turns.Add(new TurnResult(prompt, response.Text));
@@ -239,7 +246,18 @@
internal sealed record SquadQuery(string Squad = "research");
-internal sealed record AskRequest(string[] Prompts);
+internal sealed record AskRequest(string[]? Prompts, string? Prompt = null)
+{
+ public string[] GetPrompts()
+ {
+ if (Prompts is { Length: > 0 })
+ {
+ return Prompts;
+ }
+
+ return string.IsNullOrWhiteSpace(Prompt) ? [] : [Prompt];
+ }
+}
internal sealed record TurnResult(string Prompt, string? Response);
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/AppHostTests.cs
new file mode 100644
index 000000000..654392850
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/AppHostTests.cs
@@ -0,0 +1,65 @@
+using Aspire.Hosting.ApplicationModel;
+using CommunityToolkit.Aspire.Testing;
+
+namespace CommunityToolkit.Aspire.Hosting.Squad.Tests;
+
+public class AppHostTests(AspireIntegrationTestFixture fixture)
+ : IClassFixture>
+{
+ [Fact]
+ public async Task SquadSampleAppHost_StartsAndServesMetadata()
+ {
+ const string resourceName = "squad-api";
+ await fixture.ResourceNotificationService
+ .WaitForResourceHealthyAsync(resourceName)
+ .WaitAsync(TimeSpan.FromMinutes(5));
+
+ var model = fixture.App.Services.GetRequiredService();
+ var apiApp = model.Resources
+ .OfType()
+ .Single(resource => resource.Name == resourceName);
+
+ using HttpClient httpClient = new()
+ {
+ BaseAddress = new Uri(apiApp.GetEndpoint("http").Url)
+ };
+
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
+ cts.CancelAfter(TimeSpan.FromMinutes(2));
+
+ HttpResponseMessage? response = null;
+ while (!cts.IsCancellationRequested)
+ {
+ try
+ {
+ response = await httpClient.GetAsync("/", cts.Token);
+ if (response.IsSuccessStatusCode)
+ {
+ break;
+ }
+
+ response.Dispose();
+ response = null;
+ }
+ catch (HttpRequestException) when (!cts.IsCancellationRequested)
+ {
+ }
+ catch (TaskCanceledException) when (!cts.IsCancellationRequested)
+ {
+ }
+
+ await Task.Delay(TimeSpan.FromSeconds(1), cts.Token);
+ }
+
+ Assert.NotNull(response);
+ using (response)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var body = await response.Content.ReadAsStringAsync();
+ Assert.Contains("research-squad", body, StringComparison.Ordinal);
+ Assert.Contains("dev-squad", body, StringComparison.Ordinal);
+ Assert.Contains("/ask", body, StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/CommunityToolkit.Aspire.Hosting.Squad.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/CommunityToolkit.Aspire.Hosting.Squad.Tests.csproj
index dc02b8d2a..cc0828641 100644
--- a/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/CommunityToolkit.Aspire.Hosting.Squad.Tests.csproj
+++ b/tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/CommunityToolkit.Aspire.Hosting.Squad.Tests.csproj
@@ -1,6 +1,7 @@
+