M365 Agent SDK Integration#28
Conversation
There was a problem hiding this comment.
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-serviceASP.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. |
| // Use in-memory storage for conversation and user state. | ||
| builder.Services.AddSingleton<IStorage, MemoryStorage>(); |
There was a problem hiding this comment.
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).
| // 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."); | |
| } |
| // 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(); |
There was a problem hiding this comment.
/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).
| // 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(); | |
| } |
| public static class RouteExtension | ||
| { |
There was a problem hiding this comment.
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.
| Console.WriteLine($"Received message from user: {userMessage}"); | ||
| Console.WriteLine($"Conversation ID: {conversationId}"); | ||
|
|
There was a problem hiding this comment.
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).
| Console.WriteLine($"Received message from user: {userMessage}"); | |
| Console.WriteLine($"Conversation ID: {conversationId}"); |
| Console.WriteLine($"Conversation ID: {conversationId}"); | ||
|
|
||
| // Propagate session to maintain the conversation context in the orchestrator agent. | ||
| // The state is kept orcherstator-side |
There was a problem hiding this comment.
Typo in comment: orcherstator-side → orchestrator-side (or similar).
| // The state is kept orcherstator-side | |
| // The state is kept orchestrator-side |
|
|
||
| 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'."); |
There was a problem hiding this comment.
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.
| 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'."); |
|
|
||
| var botService = builder.AddProject("m365botservice", "../m365-bot-service/M365BotService.csproj") | ||
| .WithReference(orchestratorAgent).WaitFor(orchestratorAgent) | ||
| .WaitFor(orchestratorAgent) |
There was a problem hiding this comment.
There are two consecutive WaitFor(orchestratorAgent) calls; the second one is redundant. Removing the duplicate keeps the apphost definition clearer.
| .WaitFor(orchestratorAgent) |
| using System.Collections; | ||
| using Aspire.Hosting.Yarp.Transforms; | ||
| using Microsoft.Extensions.Logging; |
There was a problem hiding this comment.
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.
| using System.Collections; | |
| using Aspire.Hosting.Yarp.Transforms; | |
| using Microsoft.Extensions.Logging; | |
| using Aspire.Hosting.Yarp.Transforms; |
| #:project ../m365-bot-service/M365BotService.csproj | ||
|
|
||
| using System.Collections; | ||
| using Aspire.Hosting.Yarp.Transforms; | ||
| using Microsoft.Extensions.Logging; |
There was a problem hiding this comment.
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.
|
|
||
| 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`: |
There was a problem hiding this comment.
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.
| 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`: |
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.