Add Discord support via a custom ChannelAdapter reusing the shared WeatherAgent - #670
Conversation
Discord has no Azure Bot Service channel, so this hosts the agent directly with Discord.Net (Gateway). Incoming messages drive an Agent Framework ChatClientAgent (weather tools + WorkIQ MCP tools via dev bearer token to mcp_TeamsServer); replies render as Discord embeds. Per-channel conversation sessions. Bot token + WorkIQ token via user-secrets/env.
…herAgent Instead of a standalone Discord host with its own agent, Discord now goes through a DiscordAdapter : ChannelAdapter (modeled on the SDK A2AAdapter): ProcessActivityAsync builds a TurnContext + RunPipelineAsync to drive the SAME WeatherAgent (AgentApplication); SendActivitiesAsync renders replies as Discord embeds. A DiscordGatewayService (BackgroundService) subscribes to the Discord gateway MessageReceived event, converts messages to Activities, and calls the adapter. Slack and Discord now share one agent (weather + WorkIQ), only the adapter differs.
…ffload gateway handler, clean nullable warnings
…r ChannelAdapter that reuses the shared WeatherAgent
There was a problem hiding this comment.
Pull request overview
Adds Discord as an additional “channel” for the Agent Framework Weather + WorkIQ sample by hosting a Discord gateway in-process and translating Discord messages into SDK Activity turns that run the existing WeatherAgent (no separate agent).
Changes:
- Registers a custom
DiscordAdapter : ChannelAdapterplusDiscordGatewayService : BackgroundServiceto connect to Discord.Net and drive agent turns. - Updates
WeatherAgentto treat Discord like Slack (non-streaming) to avoid emitting partial messages, and disables OBO auth for Discord. - Adds the Discord.Net.WebSocket dependency and configures an
AutoSignInSelectorto skip OAuth auto sign-in for Discord.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/dotnet/Agent Framework/Program.cs | Wires up Discord adapter/background service and configures AutoSignIn behavior for Discord. |
| samples/dotnet/Agent Framework/AgentFrameworkWeather.csproj | Adds Discord.Net.WebSocket package reference for gateway connectivity. |
| samples/dotnet/Agent Framework/Agent/WeatherAgent.cs | Adds Discord-specific behavior (no streaming; force dev bearer-token path by disabling OBO handler). |
| samples/dotnet/Agent Framework/Adapters/DiscordGatewayService.cs | Implements Discord gateway listener that converts Discord messages into SDK Activity turns. |
| samples/dotnet/Agent Framework/Adapters/DiscordAdapter.cs | Implements custom ChannelAdapter that renders outbound replies as Discord embeds. |
Comments suppressed due to low confidence (1)
samples/dotnet/Agent Framework/Adapters/DiscordGatewayService.cs:118
- Discord turns are executed with
CancellationToken.None, so in-flight agent work (tool calls / model streaming) can continue during shutdown and delay process exit. Prefer threading thestoppingTokenfromExecuteAsyncintoOnDiscordMessageAsyncand down intoProcessActivityAsync.
var agent = scope.ServiceProvider.GetRequiredService<AgentFrameworkWeather.Agent.WeatherAgent>();
await adapter.ProcessActivityAsync(identity, activity, agent.OnTurnAsync, CancellationToken.None)
.ConfigureAwait(false);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var context = new TurnContext(this, activity, claimsIdentity); | ||
| await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false); | ||
| return null!; | ||
| } |
| _client.MessageReceived += msg => | ||
| { | ||
| _ = Task.Run(() => OnDiscordMessageAsync(msg)); | ||
| return Task.CompletedTask; | ||
| }; |
| builder.Services.AddSingleton<AutoSignInSelector>(_ => | ||
| (turnContext, cancellationToken) => | ||
| Task.FromResult(!string.Equals(turnContext.Activity.ChannelId, "discord", StringComparison.OrdinalIgnoreCase))); |
| bool isSlack = string.Equals(turnContext.Activity.ChannelId, "slack", StringComparison.OrdinalIgnoreCase); | ||
| bool isDiscord = string.Equals(turnContext.Activity.ChannelId, "discord", StringComparison.OrdinalIgnoreCase); |
…t per-turn latency
…en/discord-channeladapter # Conflicts: # samples/dotnet/Agent Framework/Agent/WeatherAgent.cs
Discord isn't an Azure Bot channel, so DiscordAdapter now provides its own IUserTokenClient (via IChannelServiceClientFactory) and overrides ProcessProactiveAsync to re-run the banked activity after sign-in completes. Renders the OAuth sign-in card as a Discord embed with a real sign-in link, and builds a bot ClaimsIdentity so the token client can authenticate. Also wires up OBO auth handlers, ForceLogout, and bumps A365 tooling to 1.1.14-preview.
|
vinchencc please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
What
Adds Discord as a channel for the weather + WorkIQ agent using a custom
DiscordAdapter : ChannelAdapter. Discord reuses the sameWeatherAgentas Slack and Teams — there is no separate agent.Why
Azure Bot Service has no built-in Discord channel, so the adapter is hosted in-process:
DiscordAdapter : ChannelAdapter— INBOUND builds aTurnContextand runs the SDK pipeline; OUTBOUND renders each reply as a Discord embed.DiscordGatewayService : BackgroundService— connects the Discord.Net gateway, translates each incoming message into anActivity, and drives the adapter. Only starts whenDiscord:BotTokenis configured.This replaces the earlier standalone Discord agent (v1), which bypassed the SDK and duplicated the agent. That approach is removed and its PR is closed.
Auth
Discord isn't an Azure Bot channel, so the custom DiscordAdapter provides its own
IUserTokenClient (via IChannelServiceClientFactory) and overrides ProcessProactiveAsync
to re-run the banked activity after sign-in completes. The OAuth card renders as a
Discord embed with a real sign-in link. WorkIQ MCP tools still use the dev bearer
token for now (wiring them to the user's OBO token is a follow-up).
Testing
Validated end to end in Discord:
What's the weather in Seattle, WA?/New York, NY→ embed card with current conditions + 5-day forecastWhat Teams am I a member of?→ real Teams data via WorkIQ MCPBuild: 2 warnings (pre-existing, in WeatherAgent), 0 errors.
Performance