microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

432lines · 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
10namespace Microsoft.Teams.Bot.Compat
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.Bot.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.Bot.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.Bot.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.Bot.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 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
205
206 GetConversationsResponse conversations = await _client.GetConversationsAsync(
207 new Uri(ServiceUrl!),
208 continuationToken,
209 AgenticIdentity,
210 convertedHeaders,
211 cancellationToken).ConfigureAwait(false);
212
213 ConversationsResult result = new()
214 {
215 ContinuationToken = conversations.ContinuationToken,
216 Conversations = conversations.Conversations?.Select(c => new Microsoft.Bot.Schema.ConversationMembers
217 {
218 Id = c.Id,
219 Members = c.Members?.Select(m => m.ToCompatChannelAccount()).ToList()
220 }).ToList()
221 };
222
223 return new HttpOperationResponse<ConversationsResult>
224 {
225 Body = result,
226 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
227 };
228 }
229
230 public async Task<HttpOperationResponse<ResourceResponse>> ReplyToActivityWithHttpMessagesAsync(string conversationId, string activityId, Activity activity, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
231 {
232 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
233
234 CoreActivity coreActivity = activity.FromCompatActivity();
235
236 // Default to the ServiceUrl from the adapter if it's not set on the activity, as ConversationClient requires it for sending activities
237 if (!string.IsNullOrWhiteSpace(ServiceUrl) && coreActivity.ServiceUrl == null)
238 {
239 coreActivity.ServiceUrl = new Uri(ServiceUrl);
240 }
241
242 coreActivity.ReplyToId = activityId;
243 coreActivity.Conversation = new Microsoft.Teams.Bot.Core.Schema.Conversation(conversationId);
244
245 SendActivityResponse? response = await _client.SendActivityAsync(coreActivity, customHeaders: convertedHeaders, cancellationToken: cancellationToken).ConfigureAwait(false);
246
247 ResourceResponse resourceResponse = new()
248 {
249 Id = response?.Id ?? string.Empty
250 };
251
252 return new HttpOperationResponse<ResourceResponse>
253 {
254 Body = resourceResponse,
255 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
256 };
257 }
258
259 public async Task<HttpOperationResponse<ResourceResponse>> SendConversationHistoryWithHttpMessagesAsync(string conversationId, Microsoft.Bot.Schema.Transcript transcript, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
260 {
261 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
262
263 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
264
265 Microsoft.Teams.Bot.Core.Transcript coreTranscript = new()
266 {
267 Activities = transcript.Activities?.Select(a => a.FromCompatActivity()).ToList()
268 };
269
270 SendConversationHistoryResponse response = await _client.SendConversationHistoryAsync(
271 conversationId,
272 coreTranscript,
273 new Uri(ServiceUrl),
274 AgenticIdentity,
275 convertedHeaders,
276 cancellationToken).ConfigureAwait(false);
277
278 ResourceResponse resourceResponse = new()
279 {
280 Id = response.Id
281 };
282
283 return new HttpOperationResponse<ResourceResponse>
284 {
285 Body = resourceResponse,
286 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
287 };
288 }
289
290 /// <summary>
291 /// Sends an activity to an existing conversation.
292 /// </summary>
293 /// <param name="conversationId">The unique identifier of the conversation. Cannot be null or whitespace.</param>
294 /// <param name="activity">The activity to send. Cannot be null.</param>
295 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
296 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
297 /// <returns>
298 /// A task that represents the asynchronous operation. The task result contains an HTTP operation response with
299 /// a <see cref="ResourceResponse"/> containing the ID of the sent activity.
300 /// </returns>
301 public async Task<HttpOperationResponse<ResourceResponse>> SendToConversationWithHttpMessagesAsync(string conversationId, Activity activity, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
302 {
303 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
304
305 CoreActivity coreActivity = activity.FromCompatActivity();
306
307 // Default to the ServiceUrl from the adapter if it's not set on the activity, as ConversationClient requires it for sending activities
308 if (!string.IsNullOrWhiteSpace(ServiceUrl) && coreActivity.ServiceUrl == null)
309 {
310 coreActivity.ServiceUrl = new Uri(ServiceUrl);
311 }
312
313 coreActivity.Conversation = new Microsoft.Teams.Bot.Core.Schema.Conversation(conversationId);
314
315 SendActivityResponse? response = await _client.SendActivityAsync(coreActivity, customHeaders: convertedHeaders, cancellationToken: cancellationToken).ConfigureAwait(false);
316
317 ResourceResponse resourceResponse = new()
318 {
319 Id = response?.Id ?? string.Empty
320 };
321
322 return new HttpOperationResponse<ResourceResponse>
323 {
324 Body = resourceResponse,
325 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
326 };
327 }
328
329 /// <summary>
330 /// Updates an existing activity in a conversation.
331 /// </summary>
332 /// <param name="conversationId">The unique identifier of the conversation. Cannot be null or whitespace.</param>
333 /// <param name="activityId">The unique identifier of the activity to update. Cannot be null or whitespace.</param>
334 /// <param name="activity">The updated activity content. Cannot be null.</param>
335 /// <param name="customHeaders">Optional custom HTTP headers to include in the request.</param>
336 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
337 /// <returns>
338 /// A task that represents the asynchronous operation. The task result contains an HTTP operation response with
339 /// a <see cref="ResourceResponse"/> containing the ID of the updated activity.
340 /// </returns>
341 public async Task<HttpOperationResponse<ResourceResponse>> UpdateActivityWithHttpMessagesAsync(string conversationId, string activityId, Activity activity, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
342 {
343 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
344
345 CoreActivity coreActivity = activity.FromCompatActivity();
346
347 // Default to the ServiceUrl from the adapter if it's not set on the activity, as ConversationClient requires it for updating activities
348 if (!string.IsNullOrWhiteSpace(ServiceUrl) && coreActivity.ServiceUrl == null)
349 {
350 coreActivity.ServiceUrl = new Uri(ServiceUrl);
351 }
352
353 UpdateActivityResponse response = await _client.UpdateActivityAsync(conversationId, activityId, coreActivity, customHeaders: convertedHeaders, cancellationToken: cancellationToken).ConfigureAwait(false);
354
355 ResourceResponse resourceResponse = new()
356 {
357 Id = response.Id
358 };
359
360 return new HttpOperationResponse<ResourceResponse>
361 {
362 Body = resourceResponse,
363 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
364 };
365 }
366
367 public async Task<HttpOperationResponse<ResourceResponse>> UploadAttachmentWithHttpMessagesAsync(string conversationId, Microsoft.Bot.Schema.AttachmentData attachmentUpload, Dictionary<string, List<string>>? customHeaders = null, CancellationToken cancellationToken = default)
368 {
369 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
370 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
371
372 Microsoft.Teams.Bot.Core.AttachmentData coreAttachmentData = new()
373 {
374 Type = attachmentUpload.Type,
375 Name = attachmentUpload.Name,
376 OriginalBase64 = attachmentUpload.OriginalBase64,
377 ThumbnailBase64 = attachmentUpload.ThumbnailBase64
378 };
379
380 UploadAttachmentResponse response = await _client.UploadAttachmentAsync(
381 conversationId,
382 coreAttachmentData,
383 new Uri(ServiceUrl),
384 AgenticIdentity,
385 convertedHeaders,
386 cancellationToken).ConfigureAwait(false);
387
388 ResourceResponse resourceResponse = new()
389 {
390 Id = response.Id
391 };
392
393 return new HttpOperationResponse<ResourceResponse>
394 {
395 Body = resourceResponse,
396 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
397 };
398 }
399
400 private static Dictionary<string, string>? ConvertHeaders(Dictionary<string, List<string>>? customHeaders)
401 {
402 if (customHeaders == null)
403 {
404 return null;
405 }
406
407 Dictionary<string, string> convertedHeaders = [];
408 foreach (KeyValuePair<string, List<string>> kvp in customHeaders)
409 {
410 convertedHeaders[kvp.Key] = string.Join(",", kvp.Value);
411 }
412
413 return convertedHeaders;
414 }
415
416 public async Task<HttpOperationResponse<ChannelAccount>> GetConversationMemberWithHttpMessagesAsync(string userId, string conversationId, Dictionary<string, List<string>> customHeaders = null!, CancellationToken cancellationToken = default)
417 {
418 ArgumentException.ThrowIfNullOrWhiteSpace(ServiceUrl);
419
420 Dictionary<string, string>? convertedHeaders = ConvertHeaders(customHeaders);
421
422 Microsoft.Teams.Bot.Core.Schema.ConversationAccount response = await _client.GetConversationMemberAsync<Microsoft.Teams.Bot.Core.Schema.ConversationAccount>(
423 conversationId, userId, new Uri(ServiceUrl), AgenticIdentity, convertedHeaders, cancellationToken).ConfigureAwait(false);
424
425 return new HttpOperationResponse<ChannelAccount>
426 {
427 Body = response.ToCompatChannelAccount(),
428 Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
429 };
430 }
431 }
432}
433