Skip to content

feat(apps): add Socket Mode to TeamsBotApplication - #686

Merged
teddy arida-moody (teddyam) merged 49 commits into
mainfrom
teddyam-socket-mode-app-integration
Oct 1, 2026
Merged

teddy arida-moody (teddyam) merged 49 commits into
mainfrom
teddyam-socket-mode-app-integration

Conversation

@teddyam

@teddyam teddy arida-moody (teddyam) commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Wires the Socket Mode transport from #682, #683, and #685 into TeamsBotApplication, so a bot can receive activities over outbound WebSocket connections instead of an HTTP messaging endpoint. Brings .NET to parity with microsoft/teams.ts#716, microsoft/teams.ts#743, microsoft/teams.ts#875, and microsoft/teams.py#608.

Stacked on #685.

Usage

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTeamsBotApplication(options => options.UseSocketMode());

var host = builder.Build();
var teams = host.UseTeamsBotApplication();
teams.OnMessage((context, ct) => context.ReplyAsync($"You said: {context.Activity.Text}", ct));
host.Run();

Socket Mode is experimental, so callers suppress ExperimentalTeamsSocketMode (for example, <NoWarn>$(NoWarn);ExperimentalTeamsSocketMode</NoWarn>).

Changes

Core

  • New BotApplication.ProcessAsync(CoreActivity, ClaimsPrincipal?, string? correlationVector, CancellationToken) overload that runs the activity pipeline without an HttpContext. The HTTP path now delegates to it.

Apps

  • Public SocketModeOptions (negotiate URL, geos, startup/readiness timeouts, reconnect delays, keep-alive, server timeout).
  • TeamsBotApplicationOptions.UseSocketMode is the only transport switch, mirroring TS/Python's boolean | SocketModeOptions:
    • UseSocketMode() or UseSocketMode(true) enables it with defaults.
    • UseSocketMode(false) clears any earlier Socket Mode configuration, so the bot uses HTTP.
    • UseSocketMode(s => ...) enables it with custom settings.
  • SocketModeOptions and UseSocketMode are marked [Experimental("ExperimentalTeamsSocketMode")], matching TS's @experimental and the repo's ExperimentalTeams<Feature> convention.
  • UseTeamsBotApplication() is the same call for both transports and reads the choice from UseSocketMode. Socket Mode runs on a generic host with no web server:
    • New UseTeamsBotApplication(this IHost) overloads for a generic host.
    • New UseTeamsBotApplication(this WebApplication, string routePath) overloads with the same parameters as the existing IEndpointRouteBuilder overloads. They exist only because WebApplication is both an IHost and an IEndpointRouteBuilder, which would otherwise make existing HTTP calls ambiguous.
    • Socket Mode on a WebApplication throws, and a host that includes a web server fails to start. HTTP on a generic host throws.
  • A hosted service starts every geo (amer, emea, apac by default) and blocks startup until all are ready.
  • Activities are dispatched straight into the shared pipeline; invoke responses are returned over the socket.
  • Options are validated when the host starts, as in Python.
  • Only the public cloud is supported, and other clouds are rejected at registration.
  • A negotiate 401/403 is not retried, matching fix(apps): stop retrying Socket Mode negotiate on 401/403 and parse envelope metadata teams.ts#875 and teams.py:
    • At startup, it fails immediately with an actionable message.
    • During a reconnect or token rotation, it stops that geo and reports it disconnected. Other geos keep running.
  • Socket Mode registers routing services. Without them, Core's authorization services fail DI validation on a generic host in Development.

Samples and docs

  • New samples/SocketModeBot: a quoted echo reply, with credentials supplied through launchSettings.TEMPLATE.json.
  • Socket Mode section in the Apps README, and the sample listed in the root README.
  • Socket Mode is currently available only on the canary ring, so the sample and README set NegotiateBaseUrl to https://canary.botapi.skype.com.

Validation

  • Unit tests cover options (including UseSocketMode(false)), hosting (generic host, the web app rejection, Development DI validation), dispatch, 401/403 handling, and an end-to-end test from a message over the socket to the reply posted to the conversation API.
  • Core 202, Apps 812, and M365Extensions 16 tests pass on net8.0 and net10.0; BotBuilder 56 pass on net10.0.
  • Manually verified against Teams with SocketModeBot: all three geos connected and the bot replied.

Teddy Arida-Moody and others added 30 commits September 22, 2026 17:15
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Refactor OnActivity to await readiness before handling activity.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds an internal GeoSocket that keeps one geo connected: initial startup
within a monotonic budget, Retry-After/backoff retries, reconnect after
unexpected closure, and make-before-break token rotation with a retiring
handoff window. Dispatch is fenced to the active or retiring generation,
and every connection is stopped and disposed exactly once.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve per-geo negotiate endpoints, start every geo with fail-fast
startup, stop all geos, aggregate status internally, and dispatch
envelopes with invoke/ack replies and 500 on handler failure.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds an internal GeoSocket that keeps one geo connected: initial startup
within a monotonic budget, Retry-After/backoff retries, reconnect after
unexpected closure, and make-before-break token rotation with a retiring
handoff window. Dispatch is fenced to the active or retiring generation,
and every connection is stopped and disposed exactly once.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve per-geo negotiate endpoints, start every geo with fail-fast
startup, stop all geos, aggregate status internally, and dispatch
envelopes with invoke/ack replies and 500 on handler failure.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Log connection stop/dispose failures instead of throwing so they cannot
mask the original startup failure or end a geo's supervisor, and mark
every geo as Stopped when the transport stops.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A faulted supervisor is already logged; rethrowing it from stop could
mask the original startup failure in the transport.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Teddy Arida-Moody and others added 6 commits September 28, 2026 15:40
The bot pipeline already logs handler failures before wrapping them in
BotHandlerException, so the transport now only logs failures the pipeline
has not logged. It still replies 500 in both cases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Echo bot that receives activities over Socket Mode on a generic host with
no web server, using UseSocketMode() and UseTeamsSocketApplication().

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…file in sample

Development enables service validation on build, which failed on a generic
host because Core's authorization services need routing that only a web app
registers. AddSocketMode now registers routing.

SocketModeBot now takes credentials from a launchSettings.json copied from
a template, like the other samples, setting DOTNET_ENVIRONMENT because a
generic host doesn't read ASPNETCORE_ENVIRONMENT. Also ignore
launchSettings.json on case-sensitive filesystems.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Socket Mode negotiate is only available on canary for now; production
returns 503. Matches the TS and Python samples' override.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ration test and docs

- Negotiate 401/403 is non-retryable with actionable messages: startup fails
  immediately, and a rejected reconnect or rotation stops that geo (including
  a connection still serving) and reports it disconnected. Other geos and
  other failures are unaffected. Matches microsoft/teams.ts#875 and teams.py.
- Add a generic-host test that sends a message over the socket and asserts
  the reply is posted through the conversation API.
- Document Socket Mode in the Apps README and list SocketModeBot in samples.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@teddyam
teddy arida-moody (teddyam) added this pull request to stack #684 September 28, 2026 23:59
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Replace UseTeamsSocketApplication with a socket flag on UseTeamsBotApplication so the
entry point no longer differs by transport. The flag must match UseSocketMode(); a
mismatch throws. A WebApplication overload keeps existing calls unambiguous.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 29, 2026 00:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The promised hosting API is absent, and Socket Mode currently discards envelope correlation vectors.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds Socket Mode integration for receiving Teams activities through outbound WebSocket connections on a headless generic host.

Changes:

  • Adds Socket Mode options, hosting, dispatch, and lifecycle handling.
  • Adds transport-neutral activity processing and invoke-response capture.
  • Adds extensive tests, documentation, and a sample bot.
File Description
.gitignore Ignores correctly cased launch settings.
Microsoft.Teams.slnx Adds the Socket Mode sample.
README.md Lists the new sample.
samples/​SocketModeBot/​Program.cs Implements the echo bot.
samples/​SocketModeBot/​README.md Documents sample setup and use.
samples/​SocketModeBot/​SocketModeBot.csproj Defines the sample project.
samples/​SocketModeBot/​appsettings.json Configures sample logging.
samples/​SocketModeBot/​Properties/​launchSettings.TEMPLATE.json Provides credential placeholders.
src/​Microsoft.Teams.Apps/​README.md Documents Socket Mode.
src/​Microsoft.Teams.Apps/​TeamsBotApplication.cs Captures invoke responses for non-HTTP transports.
src/​Microsoft.Teams.Apps/​TeamsBotApplication.HostingExtensions.cs Registers and selects hosting transports.
src/​Microsoft.Teams.Apps/​TeamsBotApplicationOptions.cs Adds Socket Mode configuration.
src/​Microsoft.Teams.Apps/​SocketMode/​GeoSocket.cs Handles terminal authorization failures.
src/​Microsoft.Teams.Apps/​SocketMode/​SocketModeHostedService.cs Manages transport lifetime.
src/​Microsoft.Teams.Apps/​SocketMode/​SocketModeNegotiator.cs Classifies authorization failures.
src/​Microsoft.Teams.Apps/​SocketMode/​SocketModeOptions.cs Defines public options.
src/​Microsoft.Teams.Apps/​SocketMode/​SocketModeProtocol.cs Uses the canary endpoint.
src/​Microsoft.Teams.Apps/​SocketMode/​SocketModeServiceRegistration.cs Wires Socket Mode into DI.
src/​Microsoft.Teams.Apps/​SocketMode/​SocketModeTransport.cs Integrates dispatch error handling.
src/​Microsoft.Teams.Core/​BotApplication.cs Adds transport-neutral processing.
test/​Microsoft.Teams.Core.UnitTests/​BotApplicationTests.cs Tests the new processing overload.
test/​Microsoft.Teams.Apps.UnitTests/​TeamsBotApplicationTests.cs Tests invoke-response capture.
test/​Microsoft.Teams.Apps.UnitTests/​SocketMode/​GeoSocketTests.cs Tests terminal authorization failures.
test/​Microsoft.Teams.Apps.UnitTests/​SocketMode/​SocketModeEndToEndTests.cs Updates endpoint expectations.
test/​Microsoft.Teams.Apps.UnitTests/​SocketMode/​SocketModeHostingTests.cs Tests hosting and end-to-end dispatch.
test/​Microsoft.Teams.Apps.UnitTests/​SocketMode/​SocketModeNegotiatorTests.cs Tests authorization classification.
test/​Microsoft.Teams.Apps.UnitTests/​SocketMode/​SocketModeOptionsTests.cs Tests public options.
test/​Microsoft.Teams.Apps.UnitTests/​SocketMode/​SocketModeTransportTests.cs Tests dispatch logging and defaults.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Microsoft.Teams.Apps/TeamsBotApplication.HostingExtensions.cs Outdated
Teddy Arida-Moody and others added 3 commits September 29, 2026 11:08
Revert the SDK default to https://botapi.skype.com to match TS and Python.
The SocketModeBot sample sets NegotiateBaseUrl to the canary ring, where
Socket Mode is available today. Also mark the sample non-packable so CI's
pack step does not try to publish it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove the socket flag from UseTeamsBotApplication. The transport is now
chosen only by UseSocketMode in AddTeamsBotApplication, and
UseTeamsBotApplication() is the same call for HTTP and Socket Mode. The
WebApplication overloads keep the same parameters as main and exist only
so a WebApplication binds unambiguously.

UseSocketMode gains a bool overload, matching TS/Python's
boolean | SocketModeOptions: UseSocketMode() or UseSocketMode(true) enables
it with defaults, UseSocketMode(false) clears earlier configuration, and
UseSocketMode(configure) enables it with custom settings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mark SocketModeOptions and UseSocketMode with
[Experimental("ExperimentalTeamsSocketMode")], matching TS's
@experimental and the repo's ExperimentalTeams<Feature> convention.
The SDK's own internal references suppress the diagnostic; the sample
and unit tests opt in with NoWarn, and the docs explain how to.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread samples/SocketModeBot/Properties/launchSettings.TEMPLATE.json
Base automatically changed from teddyam-socket-mode-geo-supervisor to main October 1, 2026 18:34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@teddyam
teddy arida-moody (teddyam) added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 995c2c3 Oct 1, 2026
6 checks passed
@teddyam
teddy arida-moody (teddyam) deleted the teddyam-socket-mode-app-integration branch October 1, 2026 22:35
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.

3 participants