microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3ddf9fa76ec1801a0e3ca312c6d9855879571ac1

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Apps.BotBuilder/CompatConversations.cs

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