microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/sub-pr-338

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Bot.Compat/CompatConversations.cs

423lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.Bot.Connector;
5using Microsoft.Bot.Schema;
6using Microsoft.Rest;
7using Microsoft.Teams.Bot.Core;
8using Microsoft.Teams.Bot.Core.Schema;
9
10// TODO: Figure out what to do with Agentic Identities. They're all "nulls" here right now.
11// The identity is dependent on the incoming payload or supplied in for proactive scenarios.
12namespace Microsoft.Teams.Bot.Compat
13{
14 /// <summary>
15 /// Provides a compatibility adapter that bridges the Teams Bot Core <see cref="ConversationClient"/> to the
16 /// Bot Framework's <see cref="IConversations"/> interface.
17 /// </summary>
18 /// <remarks>
19 /// This adapter enables legacy Bot Framework bots to use the new Teams Bot Core conversation management
20 /// without code changes. It converts between Bot Framework and Core activity formats, handles HTTP operation
21 /// responses, and manages custom header translations. All operations delegate to the underlying Core ConversationClient.
22 /// </remarks>
23 /// <param name="client">The underlying Teams Bot Core ConversationClient that performs the actual conversation operations.</param>
24 internal sealed class CompatConversations(ConversationClient client) : IConversations
25 {
26 internal readonly ConversationClient _client = client;
27
28 /// <summary>
29 /// Gets or sets the service URL for the bot service endpoint.
30 /// This URL is used for all conversation operations and must be set before making API calls.
31 /// </summary>
32 internal string? ServiceUrl { get; set; }
33
34 /// <summary>
35 /// Creates a new conversation with the specified parameters.
36 /// </summary>
37 /// <param name="parameters">The conversation parameters including members and activity. Cannot be null.</param>
38 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
39 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
40 /// <returns>
41 /// A task that represents the asynchronous operation. The task result contains an HTTP operation response with
42 /// a <see cref="ConversationResourceResponse"/> containing the conversation ID, activity ID, and service URL.
43 /// </returns>
44 /// <exception cref="ArgumentException">Thrown when <see cref="ServiceUrl"/> is null or whitespace.</exception>
45 public async Task<HttpOperationResponse<ConversationResourceResponse>> CreateConversationWithHttpMessagesAsync(
46 Microsoft.Bot.Schema.ConversationParameters parameters,
47 Dictionary<string, List<string>>? customHeaders = null,
48 CancellationToken cancellationToken = default)
49 {
50 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
51
52 Microsoft.Teams.Bot.Core.ConversationParameters convoParams = new()
53 {
54 Activity = parameters.Activity.FromCompatActivity()
55 };
56 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
57
58 CreateConversationResponse res = await _client.CreateConversationAsync(
59 convoParams,
60 new Uri(ServiceUrl),
61 AgenticIdentity.FromProperties(convoParams.Activity?.From.Properties),
62 convertedHeaders,
63 cancellationToken).ConfigureAwait(false);
64
65 ConversationResourceResponse response = new()
66 {
67 ActivityId = res.ActivityId,
68 Id = res.Id,
69 ServiceUrl = res.ServiceUrl?.ToString(),
70 };
71
72 return new HttpOperationResponse<ConversationResourceResponse>
73 {
74 Body = response,
75 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
76 };
77 }
78
79 /// <summary>
80 /// Deletes an existing activity from a conversation.
81 /// </summary>
82 /// <param name="conversationId">The unique identifier of the conversation. Cannot be null or whitespace.</param>
83 /// <param name="activityId">The unique identifier of the activity to delete. Cannot be null or whitespace.</param>
84 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
85 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
86 /// <returns>A task that represents the asynchronous operation. The task result contains an HTTP operation response.</returns>
87 /// <exception cref="ArgumentException">Thrown when <see cref="ServiceUrl"/> is null or whitespace.</exception>
88 public async Task<HttpOperationResponse> DeleteActivityWithHttpMessagesAsync(string conversationId, string activityId, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
89 {
90 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
91
92 await _client.DeleteActivityAsync(
93 conversationId,
94 activityId,
95 new Uri(ServiceUrl),
96 null!,
97 ConvertHeaders(customHeaders),
98 cancellationToken).ConfigureAwait(false);
99 return new HttpOperationResponse
100 {
101 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
102 };
103 }
104
105 public async Task<HttpOperationResponse> DeleteConversationMemberWithHttpMessagesAsync(string conversationId, string memberId, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
106 {
107 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
108
109 await _client.DeleteConversationMemberAsync(
110 conversationId,
111 memberId,
112 new Uri(ServiceUrl),
113 null!,
114 ConvertHeaders(customHeaders),
115 cancellationToken).ConfigureAwait(false);
116 return new HttpOperationResponse { Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK) };
117 }
118
119 public async Task<HttpOperationResponse<IList<ChannelAccount>>> GetActivityMembersWithHttpMessagesAsync(string conversationId, string activityId, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
120 {
121 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
122
123 IList<Microsoft.Teams.Bot.Core.Schema.ConversationAccount> members = await _client.GetActivityMembersAsync(
124 conversationId,
125 activityId,
126 new Uri(ServiceUrl!),
127 null,
128 convertedHeaders,
129 cancellationToken).ConfigureAwait(false);
130
131 List<ChannelAccount> channelAccounts = [.. members.Select(m => m.ToCompatChannelAccount())];
132
133 return new HttpOperationResponse<IList<ChannelAccount>>
134 {
135 Body = channelAccounts,
136 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
137 };
138 }
139
140 /// <summary>
141 /// Retrieves the list of members participating in a conversation.
142 /// </summary>
143 /// <param name="conversationId">The unique identifier of the conversation. Cannot be null or whitespace.</param>
144 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
145 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
146 /// <returns>
147 /// A task that represents the asynchronous operation. The task result contains an HTTP operation response with
148 /// a list of <see cref="ChannelAccount"/> objects representing the conversation members.
149 /// </returns>
150 /// <exception cref="ArgumentException">Thrown when <see cref="ServiceUrl"/> is null or whitespace.</exception>
151 public async Task<HttpOperationResponse<IList<ChannelAccount>>> GetConversationMembersWithHttpMessagesAsync(string conversationId, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
152 {
153 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
154
155 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
156
157 IList<Microsoft.Teams.Bot.Core.Schema.ConversationAccount> members = await _client.GetConversationMembersAsync(
158 conversationId,
159 new Uri(ServiceUrl),
160 null,
161 convertedHeaders,
162 cancellationToken).ConfigureAwait(false);
163
164 List<ChannelAccount> channelAccounts = [.. members.Select(m => m.ToCompatChannelAccount())];
165
166 return new HttpOperationResponse<IList<ChannelAccount>>
167 {
168 Body = channelAccounts,
169 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
170 };
171 }
172
173 public async Task<HttpOperationResponse<Microsoft.Bot.Schema.PagedMembersResult>> GetConversationPagedMembersWithHttpMessagesAsync(string conversationId, int? pageSize = null, string? continuationToken = null, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
174 {
175 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
176
177 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
178
179 Microsoft.Teams.Bot.Core.PagedMembersResult pagedMembers = await _client.GetConversationPagedMembersAsync(
180 conversationId,
181 new Uri(ServiceUrl),
182 pageSize,
183 continuationToken,
184 null,
185 convertedHeaders,
186 cancellationToken).ConfigureAwait(false);
187
188 Microsoft.Bot.Schema.PagedMembersResult result = new()
189 {
190 ContinuationToken = pagedMembers.ContinuationToken,
191 Members = pagedMembers.Members?.Select(m => m.ToCompatChannelAccount()).ToList()
192 };
193
194 return new HttpOperationResponse<Microsoft.Bot.Schema.PagedMembersResult>
195 {
196 Body = result,
197 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
198 };
199 }
200
201 public async Task<HttpOperationResponse<ConversationsResult>> GetConversationsWithHttpMessagesAsync(string? continuationToken = null, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
202 {
203 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
204
205 GetConversationsResponse conversations = await _client.GetConversationsAsync(
206 new Uri(ServiceUrl!),
207 continuationToken,
208 null,
209 convertedHeaders,
210 cancellationToken).ConfigureAwait(false);
211
212 ConversationsResult result = new()
213 {
214 ContinuationToken = conversations.ContinuationToken,
215 Conversations = conversations.Conversations?.Select(c => new Microsoft.Bot.Schema.ConversationMembers
216 {
217 Id = c.Id,
218 Members = c.Members?.Select(m => m.ToCompatChannelAccount()).ToList()
219 }).ToList()
220 };
221
222 return new HttpOperationResponse<ConversationsResult>
223 {
224 Body = result,
225 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
226 };
227 }
228
229 public async Task<HttpOperationResponse<ResourceResponse>> ReplyToActivityWithHttpMessagesAsync(string conversationId, string activityId, Activity activity, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
230 {
231 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
232
233 CoreActivity coreActivity = activity.FromCompatActivity();
234
235 // ReplyToActivity is not available in ConversationClient, use SendActivityAsync with replyToId in Properties
236 coreActivity.Properties["replyToId"] = activityId;
237 if (coreActivity.Conversation == null)
238 {
239 coreActivity.Conversation = new Microsoft.Teams.Bot.Core.Schema.Conversation { Id = conversationId };
240 }
241 else
242 {
243 coreActivity.Conversation.Id = conversationId;
244 }
245
246 SendActivityResponse response = await _client.SendActivityAsync(coreActivity, convertedHeaders, cancellationToken).ConfigureAwait(false);
247
248 ResourceResponse resourceResponse = new()
249 {
250 Id = response.Id
251 };
252
253 return new HttpOperationResponse<ResourceResponse>
254 {
255 Body = resourceResponse,
256 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
257 };
258 }
259
260 public async Task<HttpOperationResponse<ResourceResponse>> SendConversationHistoryWithHttpMessagesAsync(string conversationId, Microsoft.Bot.Schema.Transcript transcript, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
261 {
262 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
263
264 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
265
266 Microsoft.Teams.Bot.Core.Transcript coreTranscript = new()
267 {
268 Activities = transcript.Activities?.Select(a => a.FromCompatActivity() as CoreActivity).ToList()
269 };
270
271 SendConversationHistoryResponse response = await _client.SendConversationHistoryAsync(
272 conversationId,
273 coreTranscript,
274 new Uri(ServiceUrl),
275 null,
276 convertedHeaders,
277 cancellationToken).ConfigureAwait(false);
278
279 ResourceResponse resourceResponse = new()
280 {
281 Id = response.Id
282 };
283
284 return new HttpOperationResponse<ResourceResponse>
285 {
286 Body = resourceResponse,
287 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
288 };
289 }
290
291 /// <summary>
292 /// Sends an activity to an existing conversation.
293 /// </summary>
294 /// <param name="conversationId">The unique identifier of the conversation. Cannot be null or whitespace.</param>
295 /// <param name="activity">The activity to send. Cannot be null.</param>
296 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
297 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
298 /// <returns>
299 /// A task that represents the asynchronous operation. The task result contains an HTTP operation response with
300 /// a <see cref="ResourceResponse"/> containing the ID of the sent activity.
301 /// </returns>
302 public async Task<HttpOperationResponse<ResourceResponse>> SendToConversationWithHttpMessagesAsync(string conversationId, Activity activity, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
303 {
304 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
305
306 CoreActivity coreActivity = activity.FromCompatActivity();
307
308 // Ensure conversation ID is set
309 coreActivity.Conversation ??= new Microsoft.Teams.Bot.Core.Schema.Conversation { Id = conversationId };
310
311 SendActivityResponse response = await _client.SendActivityAsync(coreActivity, convertedHeaders, cancellationToken).ConfigureAwait(false);
312
313 ResourceResponse resourceResponse = new()
314 {
315 Id = response.Id
316 };
317
318 return new HttpOperationResponse<ResourceResponse>
319 {
320 Body = resourceResponse,
321 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
322 };
323 }
324
325 /// <summary>
326 /// Updates an existing activity in a conversation.
327 /// </summary>
328 /// <param name="conversationId">The unique identifier of the conversation. Cannot be null or whitespace.</param>
329 /// <param name="activityId">The unique identifier of the activity to update. Cannot be null or whitespace.</param>
330 /// <param name="activity">The updated activity content. Cannot be null.</param>
331 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
332 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
333 /// <returns>
334 /// A task that represents the asynchronous operation. The task result contains an HTTP operation response with
335 /// a <see cref="ResourceResponse"/> containing the ID of the updated activity.
336 /// </returns>
337 public async Task<HttpOperationResponse<ResourceResponse>> UpdateActivityWithHttpMessagesAsync(string conversationId, string activityId, Activity activity, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
338 {
339 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
340
341 CoreActivity coreActivity = activity.FromCompatActivity();
342
343 UpdateActivityResponse response = await _client.UpdateActivityAsync(conversationId, activityId, coreActivity, convertedHeaders, cancellationToken).ConfigureAwait(false);
344
345 ResourceResponse resourceResponse = new()
346 {
347 Id = response.Id
348 };
349
350 return new HttpOperationResponse<ResourceResponse>
351 {
352 Body = resourceResponse,
353 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
354 };
355 }
356
357 public async Task<HttpOperationResponse<ResourceResponse>> UploadAttachmentWithHttpMessagesAsync(string conversationId, Microsoft.Bot.Schema.AttachmentData attachmentUpload, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
358 {
359 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
360 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
361
362 Microsoft.Teams.Bot.Core.AttachmentData coreAttachmentData = new()
363 {
364 Type = attachmentUpload.Type,
365 Name = attachmentUpload.Name,
366 OriginalBase64 = attachmentUpload.OriginalBase64,
367 ThumbnailBase64 = attachmentUpload.ThumbnailBase64
368 };
369
370 UploadAttachmentResponse response = await _client.UploadAttachmentAsync(
371 conversationId,
372 coreAttachmentData,
373 new Uri(ServiceUrl),
374 null,
375 convertedHeaders,
376 cancellationToken).ConfigureAwait(false);
377
378 ResourceResponse resourceResponse = new()
379 {
380 Id = response.Id
381 };
382
383 return new HttpOperationResponse<ResourceResponse>
384 {
385 Body = resourceResponse,
386 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
387 };
388 }
389
390 private static Dictionary<string, string>? ConvertHeaders(Dictionary<string, List<string>>? customHeaders)
391 {
392 if (customHeaders == null)
393 {
394 return null;
395 }
396
397 Dictionary<string, string> convertedHeaders = [];
398 foreach (KeyValuePair<string, List<string>> kvp in customHeaders)
399 {
400 convertedHeaders[kvp.Key] = string.Join(",", kvp.Value);
401 }
402
403 return convertedHeaders;
404 }
405
406 public async Task<HttpOperationResponse<ChannelAccount>> GetConversationMemberWithHttpMessagesAsync(string userId, string conversationId, Dictionary<string, List<string>> customHeaders = null!, CancellationToken cancellationToken = default)
407 {
408 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
409
410 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
411
412 Microsoft.Teams.Bot.Apps.Schema.TeamsConversationAccount response = await _client.GetConversationMemberAsync<Microsoft.Teams.Bot.Apps.Schema.TeamsConversationAccount>(
413 conversationId, userId, new Uri(ServiceUrl), null!, convertedHeaders, cancellationToken).ConfigureAwait(false);
414
415 return new HttpOperationResponse<ChannelAccount>
416 {
417 Body = response.ToCompatChannelAccount(),
418 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
419 };
420
421 }
422 }
423}
424