Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions core/.claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(dotnet build:*)",
"Bash(dotnet test:*)",
"Bash(dotnet clean:*)"
]
}
}
2 changes: 1 addition & 1 deletion core/samples/CompatBot/EchoBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ await conversationClient.UpdateActivityAsync(

await Task.Delay(2000, cancellationToken);

await conversationClient.DeleteActivityAsync(cr.Conversation.Id, res.Id!, new Uri(turnContext.Activity.ServiceUrl), AgenticIdentity.FromProperties(ca.From.Properties), null, cancellationToken);
await conversationClient.DeleteActivityAsync(cr.Conversation.Id, res.Id!, new Uri(turnContext.Activity.ServiceUrl), AgenticIdentity.FromProperties(ca.From?.Properties), null, cancellationToken);

await turnContext.SendActivityAsync(MessageFactory.Text("Proactive message sent and deleted."), cancellationToken);
}
Expand Down
7 changes: 5 additions & 2 deletions core/samples/CompatBot/MyCompatMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ namespace CompatBot
{
public class MyCompatMiddleware : Microsoft.Bot.Builder.IMiddleware
{
public Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
Console.WriteLine("MyCompatMiddleware: OnTurnAsync");
Console.WriteLine(turnContext.Activity.Text);
return next(cancellationToken);

await turnContext.SendActivityAsync(MessageFactory.Text("Hello from MyCompatMiddleware!"), cancellationToken);

await next(cancellationToken).ConfigureAwait(false);
}
}
}
1 change: 1 addition & 0 deletions core/samples/CompatBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

CompatAdapter compatAdapter = (CompatAdapter)app.Services.GetRequiredService<IBotFrameworkHttpAdapter>();
compatAdapter.Use(new MyCompatMiddleware());
compatAdapter.Use(new MyCompatMiddleware());

app.MapPost("/api/messages", async (IBotFrameworkHttpAdapter adapter, IBot bot, HttpRequest request, HttpResponse response, CancellationToken ct) =>
await adapter.ProcessAsync(request, response, bot, ct));
Expand Down
4 changes: 2 additions & 2 deletions core/src/Microsoft.Teams.Bot.Apps/Schema/Entities/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ public class EntityList : List<Entity>
/// <returns></returns>
public static EntityList FromJsonArray(JsonArray? jsonArray, JsonSerializerOptions? options = null)
{
if (jsonArray == null)
if (jsonArray is null)
{
return [];
return null!;
}
EntityList entities = [];
foreach (JsonNode? item in jsonArray)
Expand Down
22 changes: 17 additions & 5 deletions core/src/Microsoft.Teams.Bot.Apps/Schema/TeamsActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,18 @@ protected TeamsActivity(CoreActivity activity) : base(activity)
{
ChannelData = new TeamsChannelData(activity.ChannelData);
}
From = new TeamsConversationAccount(activity.From);
Recipient = new TeamsConversationAccount(activity.Recipient);

if (activity.From is not null)
{
From = new TeamsConversationAccount(activity.From);
}

if (activity.Recipient is not null)
{
Recipient = new TeamsConversationAccount(activity.Recipient);
}
Conversation = new TeamsConversation(activity.Conversation);

Attachments = TeamsAttachment.FromJArray(activity.Attachments);
Entities = EntityList.FromJsonArray(activity.Entities);

Expand All @@ -104,7 +113,10 @@ internal TeamsActivity Rebase()
{
base.Attachments = this.Attachments?.ToJsonArray();
base.Entities = this.Entities?.ToJsonArray();
base.ChannelData = new TeamsChannelData(this.ChannelData);
if (this.ChannelData is not null)
{
base.ChannelData = new TeamsChannelData(this.ChannelData);
}
base.From = this.From;
base.Recipient = this.Recipient;
base.Conversation = this.Conversation;
Expand All @@ -116,12 +128,12 @@ internal TeamsActivity Rebase()
/// <summary>
/// Gets or sets the account information for the sender of the Teams conversation.
/// </summary>
[JsonPropertyName("from")] public new TeamsConversationAccount From { get; set; }
[JsonPropertyName("from")] public new TeamsConversationAccount? From { get; set; }

/// <summary>
/// Gets or sets the account information for the recipient of the Teams conversation.
/// </summary>
[JsonPropertyName("recipient")] public new TeamsConversationAccount Recipient { get; set; }
[JsonPropertyName("recipient")] public new TeamsConversationAccount? Recipient { get; set; }

/// <summary>
/// Gets or sets the conversation information for the Teams conversation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static internal IList<TeamsAttachment> FromJArray(JsonArray? jsonArray)
{
if (jsonArray is null)
{
return [];
return null!;
}
List<TeamsAttachment> attachments = [];
foreach (JsonNode? item in jsonArray)
Expand Down
49 changes: 30 additions & 19 deletions core/src/Microsoft.Teams.Bot.Compat/CompatAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Teams.Bot.Apps;
using Microsoft.Teams.Bot.Core;
using Microsoft.Teams.Bot.Core.Schema;
Expand All @@ -20,10 +21,24 @@ namespace Microsoft.Teams.Bot.Compat;
/// The adapter allows registration of middleware and error handling delegates, and supports processing HTTP requests
/// and continuing conversations. Thread safety is not guaranteed; instances should not be shared across concurrent
/// requests.</remarks>
/// <param name="botApplication">The bot application instance that handles activity processing and manages user token operations.</param>
/// <param name="compatBotAdapter">The underlying bot adapter used to interact with the bot framework and create turn contexts.</param>
public class CompatAdapter(TeamsBotApplication botApplication, CompatBotAdapter compatBotAdapter) : IBotFrameworkHttpAdapter
public class CompatAdapter : IBotFrameworkHttpAdapter
{
private readonly TeamsBotApplication _teamsBotApplication;
private readonly CompatBotAdapter _compatBotAdapter;
private readonly IServiceProvider _sp;


/// <summary>
/// Creates a new instance of the <see cref="CompatAdapter"/> class.
/// </summary>
/// <param name="sp"></param>
public CompatAdapter(IServiceProvider sp)
{
_sp = sp;
_teamsBotApplication = sp.GetRequiredService<TeamsBotApplication>();
_compatBotAdapter = sp.GetRequiredService<CompatBotAdapter>();
}

/// <summary>
/// Gets the collection of middleware components configured for the application.
/// </summary>
Expand Down Expand Up @@ -64,26 +79,22 @@ public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpRespons
ArgumentNullException.ThrowIfNull(httpRequest);
ArgumentNullException.ThrowIfNull(httpResponse);
ArgumentNullException.ThrowIfNull(bot);

CoreActivity? coreActivity = null;
botApplication.OnActivity = async (activity, cancellationToken1) =>
_teamsBotApplication.OnActivity = async (activity, cancellationToken1) =>
{
coreActivity = activity;
TurnContext turnContext = new(compatBotAdapter, activity.ToCompatActivity());
turnContext.TurnState.Add<Microsoft.Bot.Connector.Authentication.UserTokenClient>(new CompatUserTokenClient(botApplication.UserTokenClient));
CompatConnectorClient connectionClient = new(new CompatConversations(botApplication.ConversationClient) { ServiceUrl = activity.ServiceUrl?.ToString() });
TurnContext turnContext = new(_compatBotAdapter, activity.ToCompatActivity());
turnContext.TurnState.Add<Microsoft.Bot.Connector.Authentication.UserTokenClient>(new CompatUserTokenClient(_teamsBotApplication.UserTokenClient));
CompatConnectorClient connectionClient = new(new CompatConversations(_teamsBotApplication.ConversationClient) { ServiceUrl = activity.ServiceUrl?.ToString() });
turnContext.TurnState.Add<Microsoft.Bot.Connector.IConnectorClient>(connectionClient);
turnContext.TurnState.Add<Microsoft.Teams.Bot.Apps.TeamsApiClient>(botApplication.TeamsApiClient);
await bot.OnTurnAsync(turnContext, cancellationToken1).ConfigureAwait(false);
turnContext.TurnState.Add<Microsoft.Teams.Bot.Apps.TeamsApiClient>(_teamsBotApplication.TeamsApiClient);
await MiddlewareSet.ReceiveActivityWithStatusAsync(turnContext, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);
};

try
{
foreach (Microsoft.Bot.Builder.IMiddleware? middleware in MiddlewareSet)
{
botApplication.Use(new CompatAdapterMiddleware(middleware));
}

await botApplication.ProcessAsync(httpRequest.HttpContext, cancellationToken).ConfigureAwait(false);
await _teamsBotApplication.ProcessAsync(httpRequest.HttpContext, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand All @@ -92,7 +103,7 @@ public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpRespons
if (ex is BotHandlerException aex)
{
coreActivity = aex.Activity;
using TurnContext turnContext = new(compatBotAdapter, coreActivity!.ToCompatActivity());
using TurnContext turnContext = new(_compatBotAdapter, coreActivity!.ToCompatActivity());
await OnTurnError(turnContext, ex).ConfigureAwait(false);
}
else
Expand Down Expand Up @@ -124,9 +135,9 @@ public async Task ContinueConversationAsync(string botId, ConversationReference
ArgumentNullException.ThrowIfNull(reference);
ArgumentNullException.ThrowIfNull(callback);

using TurnContext turnContext = new(compatBotAdapter, reference.GetContinuationActivity());
turnContext.TurnState.Add<Microsoft.Bot.Connector.IConnectorClient>(new CompatConnectorClient(new CompatConversations(botApplication.ConversationClient) { ServiceUrl = reference.ServiceUrl }));
turnContext.TurnState.Add<Microsoft.Teams.Bot.Apps.TeamsApiClient>(botApplication.TeamsApiClient);
using TurnContext turnContext = new(_compatBotAdapter, reference.GetContinuationActivity());
turnContext.TurnState.Add<Microsoft.Bot.Connector.IConnectorClient>(new CompatConnectorClient(new CompatConversations(_teamsBotApplication.ConversationClient) { ServiceUrl = reference.ServiceUrl }));
turnContext.TurnState.Add<Microsoft.Teams.Bot.Apps.TeamsApiClient>(_teamsBotApplication.TeamsApiClient);
await callback(turnContext, cancellationToken).ConfigureAwait(false);
}
}
61 changes: 0 additions & 61 deletions core/src/Microsoft.Teams.Bot.Compat/CompatAdapterMiddleware.cs

This file was deleted.

7 changes: 5 additions & 2 deletions core/src/Microsoft.Teams.Bot.Compat/CompatBotAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ private void WriteInvokeResponseToHttpResponse(InvokeResponse? invokeResponse)
response.StatusCode = invokeResponse.Status;
using StreamWriter httpResponseStreamWriter = new(response.BodyWriter.AsStream());
using JsonTextWriter httpResponseJsonWriter = new(httpResponseStreamWriter);
logger.LogTrace("Sending Invoke Response: \n {InvokeResponse} \n", System.Text.Json.JsonSerializer.Serialize(invokeResponse.Body, _writeIndentedJsonOptions));
Microsoft.Bot.Builder.Integration.AspNet.Core.HttpHelper.BotMessageSerializer.Serialize(httpResponseJsonWriter, invokeResponse.Body);
logger.LogTrace("Sending Invoke Response: \n {InvokeResponse} with status: {Status} \n", System.Text.Json.JsonSerializer.Serialize(invokeResponse.Body, _writeIndentedJsonOptions), invokeResponse.Status);
if (invokeResponse.Body is not null)
{
Microsoft.Bot.Builder.Integration.AspNet.Core.HttpHelper.BotMessageSerializer.Serialize(httpResponseJsonWriter, invokeResponse.Body);
}
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion core/src/Microsoft.Teams.Bot.Compat/CompatConversations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public async Task<HttpOperationResponse<ConversationResourceResponse>> CreateCon
CreateConversationResponse res = await _client.CreateConversationAsync(
convoParams,
new Uri(ServiceUrl),
AgenticIdentity.FromProperties(convoParams.Activity?.From.Properties),
AgenticIdentity.FromProperties(convoParams.Activity?.From?.Properties),
convertedHeaders,
cancellationToken).ConfigureAwait(false);

Expand Down
2 changes: 1 addition & 1 deletion core/src/Microsoft.Teams.Bot.Compat/CompatTeamsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private static string GetServiceUrl(ITurnContext turnContext)
private static AgenticIdentity GetIdentity(ITurnContext turnContext)
{
var coreActivity = ((Activity)turnContext.Activity).FromCompatActivity();
return AgenticIdentity.FromProperties(coreActivity.From.Properties) ?? new AgenticIdentity();
return AgenticIdentity.FromProperties(coreActivity.From?.Properties) ?? new AgenticIdentity();
}

#endregion
Expand Down
7 changes: 4 additions & 3 deletions core/src/Microsoft.Teams.Bot.Core/ConversationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public async Task<SendActivityResponse> SendActivityAsync(CoreActivity activity,
string convId = conversationId.Length > 325 ? conversationId[..325] : conversationId;
url = $"{activity.ServiceUrl.ToString().TrimEnd('/')}/v3/conversations/{convId}/activities";
}
activity.ServiceUrl = null; // do not serialize in the outgoing payload
Copy link
Collaborator

Choose a reason for hiding this comment

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

doing this means it will always be null and not overridable by the end user. What's the tradeoff here?

Copy link
Member Author

Choose a reason for hiding this comment

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

this means that we do not serialize the ServiceURL when sending the response to APX


string body = activity.ToJson();

Expand All @@ -60,7 +61,7 @@ public async Task<SendActivityResponse> SendActivityAsync(CoreActivity activity,
HttpMethod.Post,
url,
body,
CreateRequestOptions(activity.From.GetAgenticIdentity(), "sending activity", customHeaders),
CreateRequestOptions(activity.From?.GetAgenticIdentity(), "sending activity", customHeaders),
cancellationToken).ConfigureAwait(false))!;
}

Expand Down Expand Up @@ -90,7 +91,7 @@ public async Task<UpdateActivityResponse> UpdateActivityAsync(string conversatio
HttpMethod.Put,
url,
body,
CreateRequestOptions(activity.From.GetAgenticIdentity(), "updating activity", customHeaders),
CreateRequestOptions(activity.From?.GetAgenticIdentity(), "updating activity", customHeaders),
cancellationToken).ConfigureAwait(false))!;
}

Expand Down Expand Up @@ -144,7 +145,7 @@ await DeleteActivityAsync(
activity.Conversation.Id,
activity.Id,
activity.ServiceUrl,
activity.From.GetAgenticIdentity(),
activity.From?.GetAgenticIdentity(),
customHeaders,
cancellationToken).ConfigureAwait(false);
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/Microsoft.Teams.Bot.Core/Schema/CoreActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ public class CoreActivity
/// <summary>
/// Gets or sets the account that sent this activity.
/// </summary>
[JsonPropertyName("from")] public ConversationAccount From { get; set; } = new();
[JsonPropertyName("from")] public ConversationAccount? From { get; set; }
/// <summary>
/// Gets or sets the account that should receive this activity.
/// </summary>
[JsonPropertyName("recipient")] public ConversationAccount Recipient { get; set; } = new();
[JsonPropertyName("recipient")] public ConversationAccount? Recipient { get; set; }
/// <summary>
/// Gets or sets the conversation in which this activity is taking place.
/// </summary>
Expand Down
18 changes: 10 additions & 8 deletions core/src/Microsoft.Teams.Bot.Core/Schema/CoreActivityBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,13 @@ protected CoreActivityBuilder(TActivity activity)
public TBuilder WithConversationReference(TActivity activity)
{
ArgumentNullException.ThrowIfNull(activity);
ArgumentNullException.ThrowIfNull(activity.ChannelId);
ArgumentNullException.ThrowIfNull(activity.ServiceUrl);
ArgumentNullException.ThrowIfNull(activity.Conversation);
ArgumentNullException.ThrowIfNull(activity.From);
ArgumentNullException.ThrowIfNull(activity.Recipient);

WithServiceUrl(activity.ServiceUrl);
WithChannelId(activity.ChannelId);
SetConversation(activity.Conversation);
SetFrom(activity.Recipient);
SetRecipient(activity.From);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the main motivation behind these changes? If activity.From is set shouldn't it be mapped?

Copy link
Member Author

Choose a reason for hiding this comment

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

APX does not require the Recipient, so I was thinking of using it only for TM


return (TBuilder)this;
}
Expand Down Expand Up @@ -128,9 +124,12 @@ public TBuilder WithProperty<T>(string name, T? value)
/// </summary>
/// <param name="from">The sender account.</param>
/// <returns>The builder instance for chaining.</returns>
public TBuilder WithFrom(ConversationAccount from)
public TBuilder WithFrom(ConversationAccount? from)
{
SetFrom(from);
if (from is not null)
{
SetFrom(from);
}
return (TBuilder)this;
}

Expand Down Expand Up @@ -163,7 +162,10 @@ public TBuilder WithConversation(Conversation conversation)
/// <returns>The builder instance for chaining.</returns>
public virtual TBuilder WithChannelData(ChannelData? channelData)
{
_activity.ChannelData = channelData;
if (channelData is not null)
{
_activity.ChannelData = channelData;
}
return (TBuilder)this;
}

Expand Down
Loading