Skill v1.0.0
currentTrusted Publisher100/100version: "1.0.0" name: teams-sdk-to-agents-sdk-dotnet-migration description: Use when migrating a customer's Teams SDK (teams.net) bot to the Microsoft 365 Agents SDK in .NET. Triggered by projects that depend on Microsoft.Teams.Plugins.AspNetCore, Microsoft.Teams.Apps, or Microsoft.Teams.Api and call builder.AddTeams() / app.UseTeams().
Teams SDK (teams.net) to Agents SDK Migration (.NET)
Overview
When an automation caller supplies validation tools instead of shell access, use those tools to perform the build, tests, and startup checks required below. A current passing tool result is equivalent evidence; do not request unavailable shell access or claim validation from source inspection alone. Follow the caller's read-only role when reviewing.
Migrates a Teams SDK (teams.net, https://github.com/microsoft/teams.net) bot to a Microsoft 365 Agents SDK `AgentApplication`. The Teams SDK registers fluent event delegates (teamsApp.OnMessage(async context => ...)) on an IContext<T>; the Agents SDK routes activities to handler methods on an AgentApplication subclass using ITurnContext.
This is NOT the Bot Framework migration. If the project depends on Microsoft.Bot.Builder*, use bf-to-agents-sdk-dotnet-migration instead. Trigger this skill only for teams.net projects (Microsoft.Teams.Plugins.AspNetCore, Microsoft.Teams.Apps, Microsoft.Teams.Api).
Agents SDK repository: https://github.com/microsoft/agents-for-net Teams SDK repository: https://github.com/microsoft/teams.net
Core Rules
- Preserve behavior. Keep the same commands, text matching, and responses except for user-facing references to the migrated runtime, which should say "agent" instead of "bot". Make other response changes only when required to run on the Agents SDK.
- Describe the migrated runtime as an agent in README text, catalog descriptions, and user-facing messages. Keep platform terms such as Azure Bot, bot ID, and manifest
bots[], and preserve source-derived paths and identifiers unless the caller requests a rename. - After source migration is stable, use the standalone
teams-app-manifestskill to generate, complete,
or audit the Teams app manifest from source evidence and explicit product intent. Do not embed manifest feature rules in this migration skill.
- Use stable Agents SDK packages. A caller's minimum Agents SDK version is a floor, not the
version to copy into the project. In automated sample sync, use the selectedAgentsSdkVersion in the frozen sync context. Respect a customer's explicitly selected release line. Otherwise, read the Agents SDK release history JSON (follow later pages when needed) and select the newest stable release within the permitted major, then confirm a common major.minor release line for Microsoft.Agents.Hosting.AspNetCore, Microsoft.Agents.Authentication.Msal, and Microsoft.Agents.Extensions.MSTeams from the Hosting, Authentication, and MSTeams JSON version feeds. Use the newest compatible stable line available for all three, without assuming their patch numbers match. Do not select a prerelease or a new major version without explicit policy.
- If the project defines the
AgentApplicationduring DI (typically inProgram.cs), ask the customer
whether to keep it inline or move it to an AgentApplication subclass with Teams route attributes. In the automated Teams sample sync, do not ask a user. Apply an applicable migration policy or return needs-policy with evidence and a recommended policy.
- If creating a subclass, always use
[TeamsExtension], mark itpartial, use Teams route attributes,
and take ITeamsTurnContext in route handlers.
- After restoring packages, verify route attributes, handler delegate signatures, payload models, and
response types against the installed configured-version XML documentation or current Agents SDK source. Do not infer an API shape from a similarly named Teams SDK API.
- Preserve the original registration and
if/elseprecedence. Use explicit routerankvalues
whenever multiple text or invoke routes can match the same activity.
- Prefer
SendActivityAsync("text", cancellationToken: ct)for plain text. When a reply needs entities, attachments, or other activity fields, build an activity withActivity.CreateMessageActivity()and fluent methods such as.WithText(...), then set the extra fields. Keep a factory when it adds value for a structured response. - Use the agent's logger for diagnostics instead of
Console.WriteLine; pass exceptions to structured logging rather than logging only their messages.
Package Replacements
Out-of-repo samples use PackageReferences (not ProjectReferences). Replace the single Teams SDK package with the Agents SDK packages, using the selected major.minor release line for all three. Replace RESOLVED_VERSION below with that line's floating version (for example, major.minor.*) before writing the project file:
| Remove (Teams SDK) | Add (Agents SDK) | |
|---|---|---|
Microsoft.Teams.Plugins.AspNetCore | Microsoft.Agents.Hosting.AspNetCore + Microsoft.Agents.Authentication.Msal | |
| (Teams routing surface) | Microsoft.Agents.Extensions.MSTeams | |
Microsoft.Teams.Cards (if used) | Microsoft.Teams.Cards (kept — Agents MSTeams extension reuses it) |
<ItemGroup><PackageReference Include="Microsoft.Agents.Hosting.AspNetCore" Version="RESOLVED_VERSION" /><PackageReference Include="Microsoft.Agents.Authentication.Msal" Version="RESOLVED_VERSION" /><PackageReference Include="Microsoft.Agents.Extensions.MSTeams" Version="RESOLVED_VERSION" /></ItemGroup>
Microsoft.Agents.Hosting.AspNetCore transitively brings Microsoft.Agents.Builder, Microsoft.Agents.Core, Microsoft.Agents.Connector, and Microsoft.Agents.Storage. The [TeamsExtension] source generator ships inside Microsoft.Agents.Core (analyzers folder), so NuGet consumers get it transitively — no explicit analyzer PackageReference is needed.
TargetFramework: use the repository's configured target framework, currently net10.0. Set <TargetFramework>net10.0</TargetFramework>. Set <ImplicitUsings>disable</ImplicitUsings> and add explicit using directives (matches Agents SDK sample convention).
Namespace Replacements
Teams SDK (teams.net) | Agents SDK | |
|---|---|---|
Microsoft.Teams.Plugins.AspNetCore.Extensions | Microsoft.Agents.Hosting.AspNetCore | |
Microsoft.Teams.Apps | Microsoft.Agents.Builder, Microsoft.Agents.Builder.App | |
Microsoft.Teams.Apps.Activities | Microsoft.Agents.Builder.App (routing) | |
Microsoft.Teams.Api.Activities | Microsoft.Agents.Core.Models | |
Microsoft.Teams.Api (models) | Microsoft.Agents.Core.Models | |
(Teams-specific) Microsoft.Teams.Apps.Activities.Events etc. | Microsoft.Agents.Extensions.MSTeams.* |
Migration Steps
Step 1 — Scaffold the copy
Create agentsdk-<name>/dotnet/agentsdk-<name>/ mirroring the customer's source layout. Files:
<Project>.csproj— SDK.Web,net10.0, PackageReferences above.Program.cs— Agents SDK host (Step 3).<Name>Agent.cs— theAgentApplicationsubclass (Step 4).AspNetExtensions.cs— required.AddAgentAspNetAuthentication()is not in any NuGet package;
copy it verbatim from an Agents SDK sample (src/samples/EmptyAgent/AspNetExtensions.cs in agents-for-net).
appsettings.json— non-secret Agents SDK config (Step 5); keep local credentials in an ignoredappsettings.Development.jsonor environment variables.Properties/launchSettings.json—applicationUrl: http://localhost:3978.README.md— explain the migrated Agents SDK application and its customer-specific setup.
Step 2 — Map the surface area to route attributes
Scan the Teams SDK Program.cs (or handler files) for the teamsApp.On* registrations and map each to its Teams route attribute (see the tables below). The agent is always a [TeamsExtension] partial class — OnMessage → [TeamsMessageRoute], OnConversationUpdate → [TeamsMembersAddedRoute] / [TeamsMembersRemovedRoute], plus any Teams-specific On*.
Step 3 — Program.cs
The Teams SDK bootstrap becomes the Agents SDK host:
// Teams SDKvar builder = WebApplication.CreateBuilder(args);builder.AddTeams();var webApp = builder.Build();var teamsApp = webApp.UseTeams(true);// ... teamsApp.On*(...) registrations ...webApp.Run();
// Agents SDKusing MyAgentNamespace;using Microsoft.Agents.Hosting.AspNetCore;using Microsoft.AspNetCore.Builder;WebApplicationBuilder builder = WebApplication.CreateBuilder(args);builder.AddAgentDefaults().AddAgent<MyAgent>().AddAgentAuthorization(b => b.AddAgentAspNetAuthentication());WebApplication app = builder.Build();app.UseAgents();app.MapDefaultAgentEndpoints(); // GET "/" + POST /api/messagesapp.Run();
AddAgentDefaults() registers MemoryStorage, default options, and the standard HTTP client services. Do not add builder.Services.AddHttpClient() to Program.cs. Inline teamsApp.On* lambdas move to handler methods on the agent class (Step 4).
Step 4 — The agent class
Convert inline delegates into a [TeamsExtension] partial AgentApplication subclass. Route each command with a Teams route attribute; handlers take `ITeamsTurnContext`.
This context rule applies to route attributes from Microsoft.Agents.Extensions.MSTeams.App. Base Agents SDK routes must use their documented delegate signature. For example, [ActionExecuteRoute] uses ITurnContext, not ITeamsTurnContext.
Text dispatch semantics ([TeamsMessageRoute]):
[TeamsMessageRoute("whoami")]— exact text match, case-insensitive.[TeamsMessageRoute(textRegex: "(?i)whoami")]— regex match; use this to reproduce the Teams SDK's
text.Contains(...) dispatch (add (?i) for case-insensitivity).
[TeamsMessageRoute](no argument) — catch-all; matches any message and defaults toRouteRank.Last
so specific-text routes win.
- When several
Containsbranches could overlap, setrank:(lower = higher priority) to preserve the
original if/else precedence.
using System.Threading;using System.Threading.Tasks;using Microsoft.Agents.Builder.App;using Microsoft.Agents.Builder.State;using Microsoft.Agents.Core.Models; // Activity, Mention, ActivityTypesusing Microsoft.Agents.Extensions.MSTeams; // [TeamsExtension], ITeamsTurnContextusing Microsoft.Agents.Extensions.MSTeams.App; // [TeamsMessageRoute], [TeamsMembersAddedRoute]namespace MyAgentNamespace;[TeamsExtension]public partial class MyAgent(AgentApplicationOptions options) : AgentApplication(options){[TeamsMembersAddedRoute]public async Task OnMembersAddedAsync(ITeamsTurnContext turnContext, ITurnState turnState, CancellationToken ct){foreach (var member in turnContext.Activity.MembersAdded)if (member.Id == turnContext.Activity.Recipient.Id)await turnContext.SendActivityAsync("Welcome!", cancellationToken: ct);}[TeamsMessageRoute(textRegex: "(?i)whoami", rank: 11)]public async Task WhoAmIAsync(ITeamsTurnContext turnContext, ITurnState turnState, CancellationToken ct)=> await turnContext.SendActivityAsync($"You are: {turnContext.Activity.From.Name}", cancellationToken: ct);[TeamsMessageRoute] // catch-allpublic async Task DefaultAsync(ITeamsTurnContext turnContext, ITurnState turnState, CancellationToken ct)=> await turnContext.SendActivityAsync("Welcome!", cancellationToken: ct);}
AddAgentDefaults() registers MemoryStorage and default options — no explicit IStorage needed for a basic agent. Inline teamsApp.On* lambdas move to route-attributed methods on the agent class.
Step 5 — application settings
Teams SDK reads flat Teams:ClientId/ClientSecret/TenantId (or CLIENT_ID/CLIENT_SECRET env vars). Agents SDK uses Connections + TokenValidation (+ optional AgentApplication). Carry the values across using the customer's authentication type (default below is a SingleTenant Azure Bot with ClientSecret). Keep the checked-in appsettings.json free of real credentials. Put local values in an ignored appsettings.Development.json or environment variables, and show that pattern in the README. A checked-in appsettings.json can use placeholders for identity values while keeping the secret unset:
{"TokenValidation": {"Audiences": [ "{{ClientId}}" ],"TenantId": "{{TenantId}}"},"OutboundHostValidator": {"Enabled": false,"IncludeDefaultMicrosoftHosts": true,"Hosts": []},"AgentApplication": {"StartTypingTimer": false,"RemoveRecipientMention": false,"NormalizeMentions": false},"Connections": {"ServiceConnection": {"Settings": {"AuthType": "ClientSecret","AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}","ClientId": "{{ClientId}}","ClientSecret": null,"Scopes": [ "https://api.botframework.com/.default" ]}}},"ConnectionsMap": [ { "ServiceUrl": "*", "Connection": "ServiceConnection" } ]}
For local development, put the client ID, tenant ID, and client secret in appsettings.Development.json using the same nested keys. The file can contain only the values it overrides. Ensure it is ignored by Git and run with the Development environment. An environment override can set the secret with Connections__ServiceConnection__Settings__ClientSecret. Keep identity values consistent between TokenValidation, the service connection, and any separate Graph configuration.
For other Azure Bot types (UserAssignedMSI, MultiTenant) see the bf-to-agents-sdk-dotnet-migration skill's appsettings cases and https://aka.ms/AgentsSDK-DotNetMSALAuth.
Step 6 — Generate or audit the Teams app manifest
Use the teams-app-manifest skill with the migrated source root, original manifest if present, intended Teams scopes, distribution target, and repository placeholder convention.
- Preserve an existing manifest unless a verified migrated capability requires a change.
- If the source sample has no manifest, generate one only when all required product decisions are known.
- Return manifest evidence in the transient structured
manifestReport. Do not create
manifest-evidence.md in the automated Teams sample sync.
- Report portal, bot registration, Entra, permission, and event-subscription work separately.
- Stop on
needs-input; do not guess scopes, domains, identity values, or permissions. In the automated
Teams sample sync, report this as needs-policy and leave the blocked behavior unchanged.
Step 7 — Verify
Run dotnet build with 0 warnings/errors, then run the migrated customer application and confirm it listens on http://localhost:3978 and GET / returns 200.
Also run the schema, package, evidence, and external-configuration checks required by the teams-app-manifest skill when a manifest is present or generated.
API Mapping (base conversational features)
Teams SDK (teams.net) | Agents SDK | |
|---|---|---|
IContext<T> context | ITurnContext turnContext (+ ITurnState, CancellationToken) | |
context.Activity | turnContext.Activity | |
context.Activity.Text | turnContext.Activity.Text | |
context.Activity.From / .From.Name | turnContext.Activity.From / .From.Name | |
context.Activity.Recipient?.Id | turnContext.Activity.Recipient.Id | |
context.Activity.MembersAdded | turnContext.Activity.MembersAdded | |
await context.Send("text") | await turnContext.SendActivityAsync("text", cancellationToken: ct) | |
await context.Send(messageActivity) | await turnContext.SendActivityAsync(activity, ct) | |
await context.Send(adaptiveCard) | await turnContext.SendActivityAsync(MessageFactory.Attachment(new Attachment(ContentTypes.AdaptiveCard, card)), ct) | |
new MessageActivity().WithText("t") | Activity.CreateMessageActivity().WithText("t") | |
.AddMention(member, addText: false) | build a Mention entity + assign activity.Entities = [mention] | |
context.Api (Teams REST client) | ITeamsTurnContext.Client (preferred) or TeamsExtension.GetTeamsClient(turnContext) (needs [TeamsExtension]) |
Mention example
// Teams SDKvar activity = new MessageActivity().WithText($"Hello <at>{member.Name}</at>").AddMention(member, addText: false);await context.Send(activity);
// Agents SDKvar mention = new Mention { Mentioned = member, Text = $"<at>{member.Name}</at>" };var activity = Activity.CreateMessageActivity().WithText($"Hello {mention.Text}");activity.Entities = [mention];await turnContext.SendActivityAsync(activity, ct);
Teams-specific event mapping (requires [TeamsExtension])
Mark the class [TeamsExtension] public partial class MyAgent(...) : AgentApplication(...), add the Microsoft.Agents.Extensions.MSTeams package, and use `ITeamsTurnContext` (not ITurnContext) in Teams route handlers. Each Teams SDK teamsApp.On* maps to a route attribute:
| Teams SDK event | Agents SDK route attribute | |
|---|---|---|
OnMessage | [TeamsMessageRoute] or [TeamsMessageRoute("text")] or AgentApplication.OnTeamsMessage | |
OnConversationUpdate | [TeamsConversationUpdateRoute] / [TeamsMembersAddedRoute] / [TeamsMembersRemovedRoute] | |
OnInstall | [TeamsInstallationUpdateRoute] plus an InstallationUpdateActionTypes.Add guard | |
OnInstallUpdate | [TeamsInstallationUpdateRoute] | |
OnMeetingStart | [TeamsMeetingStartRoute] | |
OnMeetingEnd | [TeamsMeetingEndRoute] | |
OnMeetingJoin | [TeamsMeetingParticipantsJoinRoute] | |
OnMeetingLeave | [TeamsMeetingParticipantsLeaveRoute] | |
OnFileConsent (Accept branch) | [TeamsFileConsentAcceptRoute] | |
OnFileConsent (Decline branch) | [TeamsFileConsentDeclineRoute] | |
OnTaskFetch | [TeamsTaskFetchRoute("value")] → returns Microsoft.Teams.Api.TaskModules.Response | |
OnTaskSubmit | [TeamsTaskSubmitRoute("value")] → returns Microsoft.Teams.Api.TaskModules.Response | |
OnMessageExtensionQuery | [TeamsQueryRoute("commandId")] | |
OnMessageExtensionSubmitAction | [TeamsSubmitActionRoute("commandId")] | |
OnMessageExtensionFetchTask | [TeamsFetchActionRoute("commandId")] | |
OnMessageExtensionQueryLink | [TeamsQueryLinkRoute] / [TeamsAnonQueryLinkRoute] | |
OnMessageExtensionSelectItem | [TeamsSelectItemRoute] | |
OnAdaptiveCardAction / Action.Execute | [ActionExecuteRoute] returning AdaptiveCardInvokeResponse | |
| Teams card button clicked invoke | [TeamsCardButtonClickedRoute] | |
| Channel created/renamed/deleted/etc. | [TeamsChannelCreatedRoute], [TeamsChannelRenamedRoute], [TeamsChannelDeletedRoute], ... | |
| Team renamed/archived/deleted/etc. | [TeamsTeamRenamedRoute], [TeamsTeamArchivedRoute], [TeamsTeamDeletedRoute], ... | |
| Message edit/delete/undelete/read | [TeamsMessageEditRoute], [TeamsMessageDeleteRoute], [TeamsMessageUndeleteRoute], [TeamsReadReceiptRoute] | |
| Config fetch/submit | [TeamsConfigFetchRoute], [TeamsConfigSubmitRoute] |
Teams route handler signature (note ITeamsTurnContext, and the extra deserialized payload parameter whose type depends on the event):
// Meeting start/end payload is Microsoft.Teams.Api.Meetings.MeetingDetails[TeamsMeetingStartRoute]public async Task OnMeetingStartAsync(ITeamsTurnContext turnContext, ITurnState turnState,Microsoft.Teams.Api.Meetings.MeetingDetails meeting, CancellationToken ct) { ... }// Participant join/leave payload is Microsoft.Agents.Extensions.MSTeams.Models.MeetingParticipantsEventDetails[TeamsMeetingParticipantsJoinRoute]public async Task OnJoinAsync(ITeamsTurnContext turnContext, ITurnState turnState,MeetingParticipantsEventDetails participants, CancellationToken ct) { ... }
teamsApp.OnInstall(...) represents only a new installation. Since [TeamsInstallationUpdateRoute] also receives remove and upgrade actions, preserve OnInstall semantics with an explicit guard:
if (turnContext.Activity.Action != InstallationUpdateActionTypes.Add){return;}
Adaptive Card Action.Execute
Do not route Action.Execute through a generic [TeamsActivityRoute("invoke")]. Teams expects an adaptive-card invoke response, and a fire-and-forget Task handler loses that response.
using Microsoft.Agents.Builder.App.AdaptiveCards;[ActionExecuteRoute("submit_name")]public Task<AdaptiveCardInvokeResponse> OnSubmitNameAsync(ITurnContext turnContext,ITurnState turnState,object data,CancellationToken cancellationToken){return Task.FromResult(AdaptiveCardInvokeResponseFactory.Message("Submitted."));}
The route's verb matches the Adaptive Card Action.Execute verb. Deserialize data using the actual card payload shape, preserve the original action behavior, and return an AdaptiveCardInvokeResponse for every path.
AI-generated content, sensitivity, and citations
Do not approximate Teams SDK AI helpers with ChannelData, generic Entity objects, or stringified JSON. Use the Agents SDK models so Teams receives the required schema:
var aiEntity = new AIEntity{AdditionalType = [AIEntity.AdditionalTypeAIGeneratedContent],UsageInfo = new SensitivityUsageInfo{Name = "Confidential",Description = "Handle according to your organization's policy."},Citation =[new ClientCitation(1,"Source title","Source summary",string.Empty,["keyword"],"https://example.com/source",ClientCitationIconName.MicrosoftWord)]};activity.Entities = [aiEntity];
AddAIGenerated()maps toAIEntity.AdditionalTypeAIGeneratedContent.- Sensitivity metadata maps to
AIEntity.UsageInfowithSensitivityUsageInfo. - Citations map to
AIEntity.CitationwithClientCitation. - Feedback buttons use the current Teams feedback-loop helper and
[TeamsFeedbackLoopRoute] for submitted feedback.
Long-running invoke work
File consent and other invoke handlers should acknowledge promptly. Do not keep the incoming HTTP request open for a long upload, and do not use its cancellation token for work that must continue after the invoke completes.
- Capture the conversation information and immutable payload needed by the operation.
- Queue the work through a background service.
- Send completion through an Agents SDK proactive continuation.
- Do not retain or use the original
ITurnContextafter the handler returns. - Do not use untracked
Task.Runas a substitute for a background service.
Meeting property renames vs. the Teams SDK event Value: .StartTime → .ScheduledStartTime, .EndTime → .ScheduledEndTime (.Title, .JoinUrl, .Id, .MSGraphResourceId are unchanged).
Cards: Teams SDK Microsoft.Teams.Cards.AdaptiveCard types are reused by the Agents MSTeams extension — keep Microsoft.Teams.Cards and send via MessageFactory.Attachment(new Attachment(ContentTypes.AdaptiveCard, card)).
Outbound Teams REST (context.Api) → ITeamsTurnContext.Client or TeamsExtension.GetTeamsClient(turnContext) (e.g. context.Api.Meetings.GetByIdAsync(id) → context.Client.Meetings.GetByIdAsync(id)). For turn/user (delegated/OBO) Graph calls use TeamsExtension.GetGraphClient(turnContext). For app-only Graph calls (e.g. meeting transcripts via OnlineMeetingTranscript.Read.All), keep the original independent app-only client — Azure.Identity + Microsoft.Graph with a ClientSecretCredential built from config — rather than GetGraphClient. (There is no TeamsInfo static helper in the Agents SDK.)
Gotcha: any external client created eagerly in the agent constructor (e.g. a ClientSecretCredential) must guard against blank/placeholder config, or the host crashes at startup. Default such config values to empty and skip client creation when unset (wrap creation in try/catch on ArgumentException as a backstop).
Common Mistakes
| Mistake | Fix | |
|---|---|---|
| Used mismatched Agents package release lines | Resolve the newest compatible stable line shared by Hosting, Authentication, and MSTeams; respect the caller's minimum and frozen sync target | |
Retained Microsoft.Agents.Extensions.MSTeams 1.0.43-beta | Replace it with the same current version used by the other Agents SDK packages | |
Used base AgentApplication + OnActivity/constructor routes instead of [TeamsExtension] + route attributes | Always mark the class [TeamsExtension] partial and use [TeamsMessageRoute] / [TeamsMembersAddedRoute] / etc. | |
Used ITurnContext in an MSTeams route handler | Route attributes from Microsoft.Agents.Extensions.MSTeams.App require ITeamsTurnContext; base routes such as [ActionExecuteRoute] use their documented ITurnContext signature | |
Routed Action.Execute through a generic invoke route | Use [ActionExecuteRoute] and return AdaptiveCardInvokeResponseFactory responses | |
Built AI metadata with ChannelData, generic entities, or JSON strings | Use AIEntity, SensitivityUsageInfo, and ClientCitation | |
| Ran a file upload inline with the invoke cancellation token | Acknowledge promptly, queue background work, and send completion through proactive continuation | |
| Treated every installation update as an install | For migrated OnInstall, handle only InstallationUpdateActionTypes.Add | |
Forgot AspNetExtensions.cs | Copy it from an Agents SDK sample — AddAgentAspNetAuthentication is not in any NuGet package | |
Left builder.AddTeams() / webApp.UseTeams() | Replace with AddAgentDefaults().AddAgent<T>()... and app.UseAgents() + app.MapDefaultAgentEndpoints() | |
Kept context.Send(...) / IContext<T> | Use turnContext.SendActivityAsync(...) / ITurnContext | |
Kept flat Teams:* auth config | Replace with Connections + TokenValidation (SingleTenant ClientSecret by default) | |
Left TargetFramework at net10.0 with ImplicitUsings | Use net8.0, disable implicit usings, add explicit usings | |
Missing MessageFactory / ActivityTypes compile errors | Add using Microsoft.Agents.Builder; and using Microsoft.Agents.Core.Models; |