Skip to content

M365 Agent SDK Integration#28

Open
Vaccarini-Lorenzo wants to merge 10 commits intotommasodotNET:mainfrom
Vaccarini-Lorenzo:main
Open

M365 Agent SDK Integration#28
Vaccarini-Lorenzo wants to merge 10 commits intotommasodotNET:mainfrom
Vaccarini-Lorenzo:main

Conversation

@Vaccarini-Lorenzo
Copy link
Copy Markdown

Added integration with Azure Bot Service using the M365 Agent SDK. This enables support for multiple frontends (bot service channels) and allows local testing through the Agents Playground emulator.

Copilot AI review requested due to automatic review settings April 14, 2026 07:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an Azure Bot Service/M365 Agent SDK-based “bot service” frontend to the City Assistant so the existing orchestrator can be reached from M365 channels (and locally via Agents Playground), while also bumping Aspire integration package versions across agents.

Changes:

  • Updated Aspire Azure AI Inference + Cosmos package references to 13.2.0* across multiple agent projects.
  • Introduced a new m365-bot-service ASP.NET Core project that hosts /api/messages, validates Bot Service JWTs (configurable), and forwards user messages to the orchestrator via A2A.
  • Extended the Aspire app host + README to include the bot service and a local Agents Playground emulator workflow.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/restaurant-agent/RestaurantAgent.csproj Bumps Aspire inference/cosmos package versions.
src/orchestrator-agent/OrchestratorAgent.csproj Bumps Aspire inference/cosmos package versions.
src/activities-agent/ActivitiesAgent.csproj Bumps Aspire inference/cosmos package versions.
src/accommodation-agent/AccommodationAgent.csproj Bumps Aspire inference/cosmos package versions.
src/m365-bot-service/M365BotService.csproj New bot-service project + package references for M365 Agent SDK hosting.
src/m365-bot-service/Program.cs Wires up Agent hosting, auth, routes, and A2A client registration.
src/m365-bot-service/appsettings.json Adds configuration template for token validation + Bot Service connection settings.
src/m365-bot-service/M365Agent.cs Implements message forwarding from Bot Framework activities to the orchestrator A2A agent.
src/m365-bot-service/Models/TokenValidationOptions.cs Options model for configurable JWT validation settings.
src/m365-bot-service/Extensions/AspNetExtensions.cs Adds JWT bearer validation configuration for Bot Service / Entra tokens.
src/m365-bot-service/Extensions/A2AAgentsExtension.cs Registers an orchestrator A2A client using Aspire-provided service URLs.
src/m365-bot-service/Extensions/RouteExension.cs Maps /api/messages endpoint for Bot Framework traffic.
src/aspire/apphost.cs Adds the bot-service project + launches Agents Playground emulator via npx.
README.md Documents architecture updates and bot-service setup (emulator + Azure Bot Service).
CityAssistant.sln Adds the new bot-service project to the solution.
.gitignore Ignores local Aspire settings/scripts.

Comment on lines +14 to +15
// Use in-memory storage for conversation and user state.
builder.Services.AddSingleton<IStorage, MemoryStorage>();
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

MemoryStorage is suitable for local dev only; in production it will lose conversation/user state on restart and won’t work correctly when the bot service is scaled out to multiple instances. Consider wiring a persistent/distributed IStorage implementation (and/or making the storage provider configurable by environment).

Suggested change
// Use in-memory storage for conversation and user state.
builder.Services.AddSingleton<IStorage, MemoryStorage>();
// Use in-memory storage for conversation and user state only during local development.
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSingleton<IStorage, MemoryStorage>();
}
else
{
throw new InvalidOperationException(
"A persistent/distributed IStorage implementation must be configured for non-development environments. " +
"MemoryStorage is supported for local development only.");
}

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +16
// FOR DEMO PURPOSES ONLY!
// In any other scenario you should use .RequireAuthorization() to trigger the JWT validation middleware
// Without enforcing authorization, any message with empty "Authorization" header will be accepted and processed by the bot.
app.MapPost("/api/messages",
async (HttpRequest request, HttpResponse response, IAgentHttpAdapter adapter, IAgent agent, CancellationToken cancellationToken) =>
{
await adapter.ProcessAsync(request, response, agent, cancellationToken);
});
//.RequireAuthorization();
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

/api/messages is mapped without .RequireAuthorization(), so the bot will accept and process unauthenticated requests even though authentication middleware is registered. To avoid accidentally deploying an open endpoint, require authorization by default and only skip it when explicitly running the local emulator (e.g., app.Environment.IsDevelopment() or a dedicated config flag).

Suggested change
// FOR DEMO PURPOSES ONLY!
// In any other scenario you should use .RequireAuthorization() to trigger the JWT validation middleware
// Without enforcing authorization, any message with empty "Authorization" header will be accepted and processed by the bot.
app.MapPost("/api/messages",
async (HttpRequest request, HttpResponse response, IAgentHttpAdapter adapter, IAgent agent, CancellationToken cancellationToken) =>
{
await adapter.ProcessAsync(request, response, agent, cancellationToken);
});
//.RequireAuthorization();
var messagesEndpoint = app.MapPost("/api/messages",
async (HttpRequest request, HttpResponse response, IAgentHttpAdapter adapter, IAgent agent, CancellationToken cancellationToken) =>
{
await adapter.ProcessAsync(request, response, agent, cancellationToken);
});
if (!app.Environment.IsDevelopment())
{
messagesEndpoint.RequireAuthorization();
}

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +5
public static class RouteExtension
{
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The filename RouteExension.cs is misspelled compared to the type name RouteExtension and the README path (RouteExtension.cs). Renaming the file to RouteExtension.cs will prevent confusion and keep docs/build artifacts consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +33
Console.WriteLine($"Received message from user: {userMessage}");
Console.WriteLine($"Conversation ID: {conversationId}");

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Avoid writing raw user messages/conversation IDs to stdout via Console.WriteLine—this can leak PII and bypass structured logging controls. Prefer injecting ILogger<M365Agent> and log at an appropriate level with redaction/minimization (or remove these logs entirely).

Suggested change
Console.WriteLine($"Received message from user: {userMessage}");
Console.WriteLine($"Conversation ID: {conversationId}");

Copilot uses AI. Check for mistakes.
Console.WriteLine($"Conversation ID: {conversationId}");

// Propagate session to maintain the conversation context in the orchestrator agent.
// The state is kept orcherstator-side
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Typo in comment: orcherstator-sideorchestrator-side (or similar).

Suggested change
// The state is kept orcherstator-side
// The state is kept orchestrator-side

Copilot uses AI. Check for mistakes.

if (string.IsNullOrEmpty(orchestratorAgentUrlString))
{
throw new InvalidOperationException("Orchestrator Agent URL is not configured. Please set the environment variable 'services__orchestrator__https__0' or 'services__orchestrator__http__0'.");
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The exception message references env vars services__orchestrator__https__0/services__orchestrator__http__0, but the code actually reads services__orchestratoragent__https__0/services__orchestratoragent__http__0. Update the message to match the real expected variable names so misconfiguration is diagnosable.

Suggested change
throw new InvalidOperationException("Orchestrator Agent URL is not configured. Please set the environment variable 'services__orchestrator__https__0' or 'services__orchestrator__http__0'.");
throw new InvalidOperationException("Orchestrator Agent URL is not configured. Please set the environment variable 'services__orchestratoragent__https__0' or 'services__orchestratoragent__http__0'.");

Copilot uses AI. Check for mistakes.

var botService = builder.AddProject("m365botservice", "../m365-bot-service/M365BotService.csproj")
.WithReference(orchestratorAgent).WaitFor(orchestratorAgent)
.WaitFor(orchestratorAgent)
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

There are two consecutive WaitFor(orchestratorAgent) calls; the second one is redundant. Removing the duplicate keeps the apphost definition clearer.

Suggested change
.WaitFor(orchestratorAgent)

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
using System.Collections;
using Aspire.Hosting.Yarp.Transforms;
using Microsoft.Extensions.Logging;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

using System.Collections; and using Microsoft.Extensions.Logging; appear unused in this file. Please remove unused usings to avoid warnings and keep apphost.cs minimal.

Suggested change
using System.Collections;
using Aspire.Hosting.Yarp.Transforms;
using Microsoft.Extensions.Logging;
using Aspire.Hosting.Yarp.Transforms;

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +17
#:project ../m365-bot-service/M365BotService.csproj

using System.Collections;
using Aspire.Hosting.Yarp.Transforms;
using Microsoft.Extensions.Logging;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Projects were updated to Aspire 13.2.0 packages, but apphost.cs still pins the AppHost SDK/packages to 13.1.1/13.0.0 via #:sdk/#:package. Aligning the AppHost SDK/package versions with the referenced project package versions will reduce the risk of restore/runtime incompatibilities.

Copilot uses AI. Check for mistakes.

Replace all `<BOT_APP_CLIENT_ID>`, `<BOT_APP_CLIENT_SECRET>`, and `<AZURE_AD_TENANT_ID>` placeholders with your actual Azure Bot registration values.

2. **Enable JWT authorization** by uncommenting `.RequireAuthorization()` in `src/m365-bot-service/Extensions/RouteExtension.cs`:
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The docs instruct uncommenting .RequireAuthorization() in src/m365-bot-service/Extensions/RouteExtension.cs, but the file in this PR is RouteExension.cs (different name). Update the README path so readers can find the correct file.

Suggested change
2. **Enable JWT authorization** by uncommenting `.RequireAuthorization()` in `src/m365-bot-service/Extensions/RouteExtension.cs`:
2. **Enable JWT authorization** by uncommenting `.RequireAuthorization()` in `src/m365-bot-service/Extensions/RouteExension.cs`:

Copilot uses AI. Check for mistakes.
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.

2 participants