Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 6 additions & 8 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,10 @@
<PackageVersion Include="AWSSDK.S3" Version="4.0.100.2" />
<PackageVersion Include="Azure.Provisioning.AppContainers" Version="1.2.0" />
<PackageVersion Include="Bitwarden.Secrets.Sdk" Version="1.0.0" />
<!-- Direct ref to GitHub.Copilot.SDK is a temporary workaround until a
Microsoft.Agents.AI.GitHub.Copilot preview ships with the buildTransitive
bridge from https://github.com/microsoft/agent-framework/pull/6457 (merged
2026-06-10). Without it, copilot.exe never lands in bin/.../runtimes/
{rid}/native/ and the SDK throws on first RunAsync. Drop this entry once
MAF cuts the next preview. -->
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.3" />
<!-- Keep an explicit GitHub.Copilot.SDK pin for the Squad sample so the split
CopilotClient assembly and native CLI payload always resolve from the
same SDK version we validate locally/CI. -->
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.8" />
Comment on lines +80 to +83
<PackageVersion Include="JsonSchema.Net" Version="7.4.0" />
<PackageVersion Include="KubernetesClient" Version="19.0.2" />
<PackageVersion Include="OllamaSharp" Version="5.4.12" />
Expand All @@ -106,7 +103,8 @@
<PackageVersion Include="RavenDB.Client" Version="6.2.1" />
<PackageVersion Include="RavenDB.TestDriver" Version="6.2.1" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="Squad.Agents.AI" Version="0.5.1-preview.16" />
<PackageVersion Include="Microsoft.Agents.AI.GitHub.Copilot" Version="1.15.0-rc1" />
<PackageVersion Include="Squad.Agents.AI" Version="0.5.6-preview.23" />
<PackageVersion Include="SurrealDb.MinimalApis.Extensions" Version="0.10.2" />
<PackageVersion Include="SurrealDb.Net" Version="0.10.2" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.3.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<!-- Squad.Agents.AI 0.2.0-preview.4 (or later) — bumped from preview.3 to pick up
the MAF 1.10.0-rc1 / GitHub.Copilot.SDK 1.0.0 GA transitive upgrade, which
is what makes the agent's runtime file-IO actually work (the older preview
was wedged on SDK 1.0.0-beta.2 + an incompatible CLI protocol). The
GitHub.Copilot.SDK direct ref is a temporary workaround — see the comment
in Directory.Packages.props. -->
<!-- Keep the Squad sample's agent packages explicit so the sample stays on the
same tested MAF adapter + Copilot SDK combination end-to-end. The direct
MAF reference overrides the older adapter that still flows transitively via
Squad.Agents.AI, while the direct Copilot SDK reference keeps the managed
SDK version aligned with that stack. -->
<PackageReference Include="Squad.Agents.AI" />
<PackageReference Include="GitHub.Copilot.SDK" />
<PackageReference Include="Microsoft.Agents.AI.GitHub.Copilot" />
<!-- MAF's buildTransitive bridge already imports GitHub.Copilot.SDK's build
targets for downstream consumers, so exclude the direct SDK build assets
here to avoid duplicate target imports. -->
<PackageReference Include="GitHub.Copilot.SDK" ExcludeAssets="build" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Check warning

Code scanning / CodeQL

Log entries created from user input Medium

This log entry depends on a
user-provided value
.
Comment thread
tamirdresher marked this conversation as resolved.
Dismissed

var turns = new List<TurnResult>();
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));
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Aspire.Hosting.ApplicationModel;
using CommunityToolkit.Aspire.Testing;

namespace CommunityToolkit.Aspire.Hosting.Squad.Tests;

public class AppHostTests(AspireIntegrationTestFixture<Projects.CommunityToolkit_Aspire_Hosting_Squad_AppHost> fixture)
: IClassFixture<AspireIntegrationTestFixture<Projects.CommunityToolkit_Aspire_Hosting_Squad_AppHost>>
{
[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<DistributedApplicationModel>();
var apiApp = model.Resources
.OfType<ProjectResource>()
.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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\..\examples\squad\CommunityToolkit.Aspire.Hosting.Squad.AppHost\CommunityToolkit.Aspire.Hosting.Squad.AppHost.csproj" />
<ProjectReference Include="..\..\src\CommunityToolkit.Aspire.Hosting.Squad\CommunityToolkit.Aspire.Hosting.Squad.csproj" />
<ProjectReference Include="..\CommunityToolkit.Aspire.Testing\CommunityToolkit.Aspire.Testing.csproj" />
</ItemGroup>
Expand Down
Loading