diff --git a/core/src/Microsoft.Teams.Apps.BotBuilder/ActivitySchemaMapper.cs b/core/src/Microsoft.Teams.Apps.BotBuilder/ActivitySchemaMapper.cs index 779059fd..c29a4cb3 100644 --- a/core/src/Microsoft.Teams.Apps.BotBuilder/ActivitySchemaMapper.cs +++ b/core/src/Microsoft.Teams.Apps.BotBuilder/ActivitySchemaMapper.cs @@ -8,6 +8,7 @@ using Microsoft.Bot.Schema.Teams; using Microsoft.Teams.Core.Schema; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.Teams.Apps.BotBuilder; @@ -69,6 +70,10 @@ public static Microsoft.Bot.Schema.ChannelAccount ToCompatChannelAccount(this Mi Name = account.Name }; + if (!string.IsNullOrEmpty(account.BotId)) + { + channelAccount.Properties.Add("botId", account.BotId); + } if (account.Properties.TryGetValue("aadObjectId", out object? aadObjectId)) { @@ -206,6 +211,11 @@ public static Microsoft.Teams.Core.Schema.ChannelAccount FromCompatChannelAccoun Microsoft.Teams.Core.Schema.ChannelAccount result = new() { Id = account.Id, Name = account.Name }; + if (account.Properties is not null && account.Properties.TryGetValue("botId", out JToken? botId)) + { + result.BotId = GetStringValue(botId); + } + if (!string.IsNullOrEmpty(account.AadObjectId)) { result.Properties["aadObjectId"] = account.AadObjectId; diff --git a/core/src/Microsoft.Teams.Apps.BotBuilder/TeamsApiClient.cs b/core/src/Microsoft.Teams.Apps.BotBuilder/TeamsApiClient.cs index cf570f6a..d1cfe2bd 100644 --- a/core/src/Microsoft.Teams.Apps.BotBuilder/TeamsApiClient.cs +++ b/core/src/Microsoft.Teams.Apps.BotBuilder/TeamsApiClient.cs @@ -45,10 +45,10 @@ private static string GetServiceUrl(ITurnContext turnContext) ?? throw new InvalidOperationException("ServiceUrl is required."); } - private static AgenticIdentity GetIdentity(ITurnContext turnContext) + private static AgenticIdentity? GetIdentity(ITurnContext turnContext) { CoreActivity coreActivity = turnContext.Activity.FromBotFrameworkActivity(); - return AgenticIdentity.FromAccount(coreActivity.From) ?? new AgenticIdentity(); + return AgenticIdentity.FromAccount(coreActivity.Recipient); } #endregion @@ -87,7 +87,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); Core.Schema.ChannelAccount result = await client.GetConversationMemberAsync( conversationId, userId, serviceUrl, BotRequestContext.FromAgenticIdentity(identity), null, cancellationToken).ConfigureAwait(false); @@ -121,7 +121,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); IList members = await client.GetConversationMembersAsync( conversationId, serviceUrl, BotRequestContext.FromAgenticIdentity(identity), null, cancellationToken).ConfigureAwait(false); @@ -158,7 +158,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); Core.PagedMembersResult pagedMembers = await client.GetConversationPagedMembersAsync( conversationId, serviceUrl, pageSize, continuationToken, BotRequestContext.FromAgenticIdentity(identity), null, cancellationToken).ConfigureAwait(false); @@ -192,7 +192,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); Core.Schema.ChannelAccount result = await client.GetConversationMemberAsync( t, userId, serviceUrl, BotRequestContext.FromAgenticIdentity(identity), null, cancellationToken).ConfigureAwait(false); @@ -220,7 +220,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); IList members = await client.GetConversationMembersAsync( t, serviceUrl, BotRequestContext.FromAgenticIdentity(identity), null, cancellationToken).ConfigureAwait(false); @@ -251,7 +251,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); Core.PagedMembersResult pagedMembers = await client.GetConversationPagedMembersAsync( t, serviceUrl, pageSize, continuationToken, BotRequestContext.FromAgenticIdentity(identity), null, cancellationToken).ConfigureAwait(false); @@ -280,7 +280,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting."); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); ConversationClient client = GetConversationClient(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}"; @@ -319,7 +319,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}/participants/{Uri.EscapeDataString(participantId)}?tenantId={Uri.EscapeDataString(tenantId)}"; @@ -353,7 +353,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v1/meetings/{Uri.EscapeDataString(meetingId)}/notification"; string body = JsonConvert.SerializeObject(notification); @@ -387,7 +387,7 @@ private static AgenticIdentity GetIdentity(ITurnContext turnContext) ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team."); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/teams/{Uri.EscapeDataString(t)}"; @@ -419,7 +419,7 @@ public static async Task GetTeamChannelsAsync( ?? throw new InvalidOperationException("This method is only valid within the scope of MS Teams Team."); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity identity = GetIdentity(turnContext); + AgenticIdentity? identity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/teams/{Uri.EscapeDataString(t)}/conversations"; @@ -461,7 +461,7 @@ public static async Task SendMessageToListOfUsersAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/users/"; SendMessageToUsersRequest request = new() @@ -503,7 +503,7 @@ public static async Task SendMessageToListOfChannelsAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/channels/"; SendMessageToUsersRequest request = new() { @@ -545,7 +545,7 @@ public static async Task SendMessageToAllUsersInTeamAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); if (activity is not Activity teamActivity) throw new ArgumentException("Expected a Bot Framework Activity instance.", nameof(activity)); CoreActivity coreActivity = teamActivity.FromBotFrameworkActivity(); @@ -588,7 +588,7 @@ public static async Task SendMessageToAllUsersInTenantAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); if (activity is not Activity tenantActivity) throw new ArgumentException("Expected a Bot Framework Activity instance.", nameof(activity)); CoreActivity coreActivity = tenantActivity.FromBotFrameworkActivity(); @@ -684,7 +684,7 @@ await turnContext.Adapter.CreateConversationAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}"; @@ -715,7 +715,7 @@ await turnContext.Adapter.CreateConversationAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/failedentries/{Uri.EscapeDataString(operationId)}"; @@ -749,7 +749,7 @@ public static async Task CancelOperationAsync( ConversationClient client = GetConversationClient(turnContext); Uri serviceUrl = new(GetServiceUrl(turnContext)); - AgenticIdentity agenticIdentity = GetIdentity(turnContext); + AgenticIdentity? agenticIdentity = GetIdentity(turnContext); string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/batch/conversation/{Uri.EscapeDataString(operationId)}"; diff --git a/core/src/Microsoft.Teams.Core/Http/BotRequestContext.cs b/core/src/Microsoft.Teams.Core/Http/BotRequestContext.cs index d78685d5..c618bc05 100644 --- a/core/src/Microsoft.Teams.Core/Http/BotRequestContext.cs +++ b/core/src/Microsoft.Teams.Core/Http/BotRequestContext.cs @@ -53,7 +53,7 @@ public record BotRequestContext /// The inbound activity, or null. /// The context, or null when nothing could be derived. public static BotRequestContext? FromInboundActivity(CoreActivity? activity) - => Build(Schema.AgenticIdentity.FromAccount(activity?.Recipient), NormalizeAppId(activity?.Recipient?.Id)); + => Build(Schema.AgenticIdentity.FromAccount(activity?.Recipient), NormalizeAppId(activity?.Recipient?.BotId)); /// /// Builds context carrying only the supplied agentic identity. diff --git a/core/src/Microsoft.Teams.Core/Schema/ChannelAccount.cs b/core/src/Microsoft.Teams.Core/Schema/ChannelAccount.cs index 7e466b46..b31b67bf 100644 --- a/core/src/Microsoft.Teams.Core/Schema/ChannelAccount.cs +++ b/core/src/Microsoft.Teams.Core/Schema/ChannelAccount.cs @@ -18,6 +18,12 @@ public class ChannelAccount() [JsonPropertyName("id")] public string? Id { get; set; } + /// + /// Gets or sets the bot application ID associated with the account. + /// + [JsonPropertyName("botId")] + public string? BotId { get; set; } + /// /// Gets or sets the display name of the channel account. /// diff --git a/core/src/Microsoft.Teams.Core/Schema/CoreActivity.cs b/core/src/Microsoft.Teams.Core/Schema/CoreActivity.cs index 26526e96..5de01676 100644 --- a/core/src/Microsoft.Teams.Core/Schema/CoreActivity.cs +++ b/core/src/Microsoft.Teams.Core/Schema/CoreActivity.cs @@ -170,11 +170,13 @@ protected CoreActivity(CoreActivity activity) private static ChannelAccount CloneChannelAccount(ChannelAccount source) => new() { Id = source.Id, + BotId = source.BotId, Name = source.Name, IsTargeted = source.IsTargeted, AgenticAppId = source.AgenticAppId, AgenticUserId = source.AgenticUserId, AgenticAppBlueprintId = source.AgenticAppBlueprintId, + TenantId = source.TenantId, Properties = new ExtendedPropertiesDictionary(source.Properties) }; #pragma warning restore ExperimentalTeamsTargeted diff --git a/core/test/Microsoft.Teams.Apps.BotBuilder.UnitTests/CompatActivityTests.cs b/core/test/Microsoft.Teams.Apps.BotBuilder.UnitTests/CompatActivityTests.cs index 6073f077..3edb4e79 100644 --- a/core/test/Microsoft.Teams.Apps.BotBuilder.UnitTests/CompatActivityTests.cs +++ b/core/test/Microsoft.Teams.Apps.BotBuilder.UnitTests/CompatActivityTests.cs @@ -7,6 +7,7 @@ using Microsoft.Bot.Schema; using Microsoft.Teams.Core.Schema; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.Teams.Apps.BotBuilder.UnitTests { @@ -311,6 +312,17 @@ public void FromCompatChannelAccount_MapsIdAndName() Assert.Equal("Alice", result.Name); } + [Fact] + public void FromCompatChannelAccount_MapsBotId() + { + Microsoft.Bot.Schema.ChannelAccount account = new() { Id = "bot-account-id" }; + account.Properties["botId"] = "28:bot-app-id"; + + Microsoft.Teams.Core.Schema.ChannelAccount result = account.FromCompatChannelAccount(); + + Assert.Equal("28:bot-app-id", result.BotId); + } + [Fact] public void FromCompatChannelAccount_MapsAadObjectIdToProperties() { @@ -350,6 +362,17 @@ public void FromCompatChannelAccount_ThrowsOnNull() Microsoft.Bot.Schema.ChannelAccount? account = null; Assert.Throws(() => account!.FromCompatChannelAccount()); } + + [Fact] + public void ToCompatChannelAccount_MapsBotId() + { + Microsoft.Teams.Core.Schema.ChannelAccount account = new() { Id = "bot-account-id", BotId = "28:bot-app-id" }; + + Microsoft.Bot.Schema.ChannelAccount result = account.ToCompatChannelAccount(); + + Assert.True(result.Properties.TryGetValue("botId", out JToken? botId)); + Assert.Equal("28:bot-app-id", botId?.ToString()); + } } public class FromCompatConversationParametersTests diff --git a/core/test/Microsoft.Teams.Core.UnitTests/Http/BotRequestContextTests.cs b/core/test/Microsoft.Teams.Core.UnitTests/Http/BotRequestContextTests.cs index 8b60c893..2792adc8 100644 --- a/core/test/Microsoft.Teams.Core.UnitTests/Http/BotRequestContextTests.cs +++ b/core/test/Microsoft.Teams.Core.UnitTests/Http/BotRequestContextTests.cs @@ -115,7 +115,7 @@ public void FromInboundActivity_TakesBotAppIdAndAgenticFromRecipient() { Type = ActivityType.Message, From = new ChannelAccount { Id = "user-id" }, - Recipient = new ChannelAccount { Id = "28:recipient-bot-id", AgenticUserId = "agentic-user" }, + Recipient = new ChannelAccount { Id = "recipient-account-id", BotId = "28:recipient-bot-id", AgenticUserId = "agentic-user" }, }; BotRequestContext? ctx = BotRequestContext.FromInboundActivity(activity); @@ -133,7 +133,7 @@ public void FromInboundActivity_IgnoresAgenticFieldsOnSender() { Type = ActivityType.Message, From = new ChannelAccount { Id = "user-id", AgenticUserId = "agentic-user" }, - Recipient = new ChannelAccount { Id = "28:recipient-bot-id" }, + Recipient = new ChannelAccount { Id = "recipient-account-id", BotId = "28:recipient-bot-id" }, }; BotRequestContext? ctx = BotRequestContext.FromInboundActivity(activity);