microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
99620f7f6ecccb78c4ebfb54686bb2a4e56dcea8

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Bot.Core/ConversationClient.cs

578lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5using Microsoft.Extensions.Logging;
6using Microsoft.Teams.Bot.Core.Http;
7using Microsoft.Teams.Bot.Core.Schema;
8
9namespace Microsoft.Teams.Bot.Core;
10
11using CustomHeaders = Dictionary<string, string>;
12
13/// <summary>
14/// Provides methods for sending activities to a conversation endpoint using HTTP requests.
15/// </summary>
16/// <param name="httpClient">The HTTP client instance used to send requests to the conversation service. Must not be null.</param>
17/// <param name="logger">The logger instance used for logging. Optional.</param>
18public class ConversationClient(HttpClient httpClient, ILogger<ConversationClient> logger = default!)
19{
20 private readonly BotHttpClient _botHttpClient = new(httpClient, logger);
21 private readonly JsonSerializerOptions _jsonSerializerOptions = new()
22 {
23 PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
24 WriteIndented = false,
25 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
26 };
27
28 internal const string ConversationHttpClientName = "BotConversationClient";
29
30 internal BotHttpClient BotHttpClient => _botHttpClient;
31
32 /// <summary>
33 /// Gets the default custom headers that will be included in all requests.
34 /// </summary>
35 public CustomHeaders DefaultCustomHeaders { get; } = [];
36
37 /// <summary>
38 /// Sends the specified activity to the conversation endpoint asynchronously.
39 /// </summary>
40 /// <param name="activity">The activity to send. Cannot be null. The activity must contain valid conversation and service URL information.</param>
41 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
42 /// <param name="cancellationToken">A cancellation token that can be used to cancel the send operation.</param>
43 /// <returns>A task that represents the asynchronous operation. The task result contains the response with the ID of the sent activity.</returns>
44 /// <exception cref="Exception">Thrown if the activity could not be sent successfully. The exception message includes the HTTP status code and
45 /// response content.</exception>
46 public virtual async Task<SendActivityResponse?> SendActivityAsync(CoreActivity activity, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
47 {
48 ArgumentNullException.ThrowIfNull(activity);
49 ArgumentNullException.ThrowIfNull(activity.Conversation);
50 ArgumentException.ThrowIfNullOrWhiteSpace(activity.Conversation.Id);
51 ArgumentNullException.ThrowIfNull(activity.ServiceUrl);
52
53 string url = $"{activity.ServiceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(activity.Conversation.Id)}/activities/";
54
55 if (activity.ChannelId == "agents")
56 {
57 logger.LogInformation("Truncating conversation ID for 'agents' channel to comply with length restrictions.");
58 string conversationId = activity.Conversation.Id;
59 string convId = conversationId.Length > 100 ? conversationId[..100] : conversationId;
60 url = $"{activity.ServiceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(convId)}/activities/";
61 }
62
63 if (!string.IsNullOrEmpty(activity.ReplyToId))
64 {
65 url += activity.ReplyToId;
66 }
67
68 if (activity.Recipient?.IsTargeted == true)
69 {
70 url += url.Contains('?', StringComparison.Ordinal) ? "&isTargetedActivity=true" : "?isTargetedActivity=true";
71 }
72
73 string body = activity.ToJson();
74
75 return await _botHttpClient.SendAsync<SendActivityResponse>(
76 HttpMethod.Post,
77 url,
78 body,
79 CreateRequestOptions(activity.From?.GetAgenticIdentity(), "sending activity", customHeaders),
80 cancellationToken).ConfigureAwait(false);
81 }
82
83 /// <summary>
84 /// Updates an existing activity in a conversation.
85 /// </summary>
86 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
87 /// <param name="activityId">The ID of the activity to update. Cannot be null or whitespace.</param>
88 /// <param name="activity">The updated activity data. Cannot be null.</param>
89 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
90 /// <param name="cancellationToken">A cancellation token that can be used to cancel the update operation.</param>
91 /// <returns>A task that represents the asynchronous operation. The task result contains the response with the ID of the updated activity.</returns>
92 /// <exception cref="HttpRequestException">Thrown if the activity could not be updated successfully.</exception>
93 public virtual async Task<UpdateActivityResponse> UpdateActivityAsync(string conversationId, string activityId, CoreActivity activity, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
94 {
95 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
96 ArgumentException.ThrowIfNullOrWhiteSpace(activityId);
97 ArgumentNullException.ThrowIfNull(activity);
98 ArgumentNullException.ThrowIfNull(activity.ServiceUrl);
99
100 string url = $"{activity.ServiceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/{Uri.EscapeDataString(activityId)}";
101
102 if (activity.Recipient?.IsTargeted == true)
103 {
104 url += "?isTargetedActivity=true";
105 }
106
107 string body = activity.ToJson();
108
109 logger.LogTraceGuarded("Updating activity at {Url}: {Activity}", url, body);
110
111 return (await _botHttpClient.SendAsync<UpdateActivityResponse>(
112 HttpMethod.Put,
113 url,
114 body,
115 CreateRequestOptions(activity.From?.GetAgenticIdentity(), "updating activity", customHeaders),
116 cancellationToken).ConfigureAwait(false))!;
117 }
118
119
120 /// <summary>
121 /// Updates an existing targeted activity in a conversation.
122 /// The activity body is sent with the targeted recipient to avoid "Cannot edit Recipient of Targeted Message" errors.
123 /// </summary>
124 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
125 /// <param name="activityId">The ID of the activity to update. Cannot be null or whitespace.</param>
126 /// <param name="activity">The updated activity data. Cannot be null. Must contain a valid ServiceUrl.</param>
127 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
128 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
129 /// <param name="cancellationToken">A cancellation token that can be used to cancel the update operation.</param>
130 /// <returns>A task that represents the asynchronous operation. The task result contains the response with the ID of the updated activity.</returns>
131 /// <exception cref="HttpRequestException">Thrown if the activity could not be updated successfully.</exception>
132 public virtual async Task<UpdateActivityResponse> UpdateTargetedActivityAsync(string conversationId, string activityId, CoreActivity activity, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
133 {
134 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
135 ArgumentException.ThrowIfNullOrWhiteSpace(activityId);
136 ArgumentNullException.ThrowIfNull(activity);
137 ArgumentNullException.ThrowIfNull(activity.ServiceUrl);
138
139 string url = $"{activity.ServiceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/{Uri.EscapeDataString(activityId)}?isTargetedActivity=true";
140
141 string body = activity.ToJson();
142
143 logger.LogTraceGuarded("Updating targeted activity at {Url}: {Activity}", url, body);
144
145 return (await _botHttpClient.SendAsync<UpdateActivityResponse>(
146 HttpMethod.Put,
147 url,
148 body,
149 CreateRequestOptions(agenticIdentity, "updating targeted activity", customHeaders),
150 cancellationToken).ConfigureAwait(false))!;
151 }
152
153 /// <summary>
154 /// Deletes an existing targeted activity from a conversation.
155 /// </summary>
156 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
157 /// <param name="activityId">The ID of the activity to delete. Cannot be null or whitespace.</param>
158 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
159 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
160 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
161 /// <param name="cancellationToken">A cancellation token that can be used to cancel the delete operation.</param>
162 /// <returns>A task that represents the asynchronous operation.</returns>
163 /// <exception cref="HttpRequestException">Thrown if the activity could not be deleted successfully.</exception>
164 public virtual Task DeleteTargetedActivityAsync(string conversationId, string activityId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
165 => DeleteActivityAsync(conversationId, activityId, serviceUrl, isTargeted: true, agenticIdentity, customHeaders, cancellationToken);
166
167 /// <summary>
168 /// Deletes an existing activity from a conversation.
169 /// </summary>
170 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
171 /// <param name="activityId">The ID of the activity to delete. Cannot be null or whitespace.</param>
172 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
173 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
174 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
175 /// <param name="cancellationToken">A cancellation token that can be used to cancel the delete operation.</param>
176 /// <returns>A task that represents the asynchronous operation.</returns>
177 /// <exception cref="HttpRequestException">Thrown if the activity could not be deleted successfully.</exception>
178 public virtual Task DeleteActivityAsync(string conversationId, string activityId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
179 => DeleteActivityAsync(conversationId, activityId, serviceUrl, isTargeted: false, agenticIdentity, customHeaders, cancellationToken);
180
181 /// <summary>
182 /// Deletes an existing activity from a conversation.
183 /// </summary>
184 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
185 /// <param name="activityId">The ID of the activity to delete. Cannot be null or whitespace.</param>
186 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
187 /// <param name="isTargeted">If true, deletes a targeted activity.</param>
188 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
189 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
190 /// <param name="cancellationToken">A cancellation token that can be used to cancel the delete operation.</param>
191 /// <returns>A task that represents the asynchronous operation.</returns>
192 /// <exception cref="HttpRequestException">Thrown if the activity could not be deleted successfully.</exception>
193 public async Task DeleteActivityAsync(string conversationId, string activityId, Uri serviceUrl, bool isTargeted, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
194 {
195 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
196 ArgumentException.ThrowIfNullOrWhiteSpace(activityId);
197 ArgumentNullException.ThrowIfNull(serviceUrl);
198
199 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/{Uri.EscapeDataString(activityId)}";
200
201 if (isTargeted)
202 {
203 url += "?isTargetedActivity=true";
204 }
205
206 await _botHttpClient.SendAsync(
207 HttpMethod.Delete,
208 url,
209 body: null,
210 CreateRequestOptions(agenticIdentity, "deleting activity", customHeaders),
211 cancellationToken).ConfigureAwait(false);
212 }
213
214 /// <summary>
215 /// Deletes an existing activity from a conversation using activity context.
216 /// </summary>
217 /// <param name="activity">The activity to delete. Must contain valid Id, Conversation.Id, and ServiceUrl. Cannot be null.</param>
218 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
219 /// <param name="cancellationToken">A cancellation token that can be used to cancel the delete operation.</param>
220 /// <returns>A task that represents the asynchronous operation.</returns>
221 /// <exception cref="HttpRequestException">Thrown if the activity could not be deleted successfully.</exception>
222 public virtual async Task DeleteActivityAsync(CoreActivity activity, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
223 {
224 ArgumentNullException.ThrowIfNull(activity);
225 ArgumentException.ThrowIfNullOrWhiteSpace(activity.Id);
226 ArgumentNullException.ThrowIfNull(activity.Conversation);
227 ArgumentException.ThrowIfNullOrWhiteSpace(activity.Conversation.Id);
228 ArgumentNullException.ThrowIfNull(activity.ServiceUrl);
229
230 await DeleteActivityAsync(
231 activity.Conversation.Id,
232 activity.Id,
233 activity.ServiceUrl,
234 activity.Recipient?.IsTargeted == true,
235 activity.From?.GetAgenticIdentity(),
236 customHeaders,
237 cancellationToken).ConfigureAwait(false);
238 }
239
240 /// <summary>
241 /// Gets the members of a conversation.
242 /// </summary>
243 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
244 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
245 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
246 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
247 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
248 /// <returns>A task that represents the asynchronous operation. The task result contains a list of conversation members.</returns>
249 /// <exception cref="HttpRequestException">Thrown if the members could not be retrieved successfully.</exception>
250 public virtual async Task<IList<ConversationAccount>> GetConversationMembersAsync(string conversationId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
251 {
252 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
253 ArgumentNullException.ThrowIfNull(serviceUrl);
254
255 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/members";
256
257 return (await _botHttpClient.SendAsync<IList<ConversationAccount>>(
258 HttpMethod.Get,
259 url,
260 body: null,
261 CreateRequestOptions(agenticIdentity, "getting conversation members", customHeaders),
262 cancellationToken).ConfigureAwait(false))!;
263 }
264
265
266 /// <summary>
267 /// Gets a specific member of a conversation with strongly-typed result.
268 /// </summary>
269 /// <typeparam name="T">The type of conversation account to return. Must inherit from <see cref="ConversationAccount"/>.</typeparam>
270 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
271 /// <param name="userId">The ID of the user to retrieve. Cannot be null or whitespace.</param>
272 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
273 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
274 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
275 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
276 /// <returns>
277 /// A task that represents the asynchronous operation. The task result contains the conversation member
278 /// of type T with detailed information about the user.
279 /// </returns>
280 /// <exception cref="HttpRequestException">Thrown if the member could not be retrieved successfully.</exception>
281 public virtual async Task<T> GetConversationMemberAsync<T>(string conversationId, string userId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default) where T : ConversationAccount
282 {
283 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
284 ArgumentNullException.ThrowIfNull(serviceUrl);
285 ArgumentException.ThrowIfNullOrWhiteSpace(userId);
286
287 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/members/{Uri.EscapeDataString(userId)}";
288
289 return (await _botHttpClient.SendAsync<T>(
290 HttpMethod.Get,
291 url,
292 body: null,
293 CreateRequestOptions(agenticIdentity, "getting conversation member", customHeaders),
294 cancellationToken).ConfigureAwait(false))!;
295 }
296
297 /// <summary>
298 /// Gets the conversations in which the bot has participated.
299 /// </summary>
300 /// <param name="serviceUrl">The service URL for the bot. Cannot be null.</param>
301 /// <param name="continuationToken">Optional continuation token for pagination.</param>
302 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
303 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
304 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
305 /// <returns>A task that represents the asynchronous operation. The task result contains the conversations and an optional continuation token.</returns>
306 /// <exception cref="HttpRequestException">Thrown if the conversations could not be retrieved successfully.</exception>
307 public virtual async Task<GetConversationsResponse> GetConversationsAsync(Uri serviceUrl, string? continuationToken = null, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
308 {
309 ArgumentNullException.ThrowIfNull(serviceUrl);
310
311 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations";
312 if (!string.IsNullOrWhiteSpace(continuationToken))
313 {
314 url += $"?continuationToken={Uri.EscapeDataString(continuationToken)}";
315 }
316
317 return (await _botHttpClient.SendAsync<GetConversationsResponse>(
318 HttpMethod.Get,
319 url,
320 body: null,
321 CreateRequestOptions(agenticIdentity, "getting conversations", customHeaders),
322 cancellationToken).ConfigureAwait(false))!;
323 }
324
325 /// <summary>
326 /// Gets the members of a specific activity.
327 /// </summary>
328 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
329 /// <param name="activityId">The ID of the activity. Cannot be null or whitespace.</param>
330 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
331 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
332 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
333 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
334 /// <returns>A task that represents the asynchronous operation. The task result contains a list of members for the activity.</returns>
335 /// <exception cref="HttpRequestException">Thrown if the activity members could not be retrieved successfully.</exception>
336 public virtual async Task<IList<ConversationAccount>> GetActivityMembersAsync(string conversationId, string activityId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
337 {
338 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
339 ArgumentException.ThrowIfNullOrWhiteSpace(activityId);
340 ArgumentNullException.ThrowIfNull(serviceUrl);
341
342 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/{Uri.EscapeDataString(activityId)}/members";
343
344 return (await _botHttpClient.SendAsync<IList<ConversationAccount>>(
345 HttpMethod.Get,
346 url,
347 body: null,
348 CreateRequestOptions(agenticIdentity, "getting activity members", customHeaders),
349 cancellationToken).ConfigureAwait(false))!;
350 }
351
352 /// <summary>
353 /// Creates a new conversation.
354 /// </summary>
355 /// <param name="parameters">The parameters for creating the conversation. Cannot be null.</param>
356 /// <param name="serviceUrl">The service URL for the bot. Cannot be null.</param>
357 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
358 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
359 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
360 /// <returns>A task that represents the asynchronous operation. The task result contains the conversation resource response with the conversation ID.</returns>
361 /// <exception cref="HttpRequestException">Thrown if the conversation could not be created successfully.</exception>
362 public virtual async Task<CreateConversationResponse> CreateConversationAsync(ConversationParameters parameters, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
363 {
364 ArgumentNullException.ThrowIfNull(parameters);
365 ArgumentNullException.ThrowIfNull(serviceUrl);
366
367 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations";
368
369 string paramsJson = JsonSerializer.Serialize(parameters, _jsonSerializerOptions);
370
371 logger.LogTraceGuarded("Creating conversation at {Url} with parameters: {Parameters}", url, paramsJson);
372
373 return (await _botHttpClient.SendAsync<CreateConversationResponse>(
374 HttpMethod.Post,
375 url,
376 paramsJson,
377 CreateRequestOptions(agenticIdentity, "creating conversation", customHeaders),
378 cancellationToken).ConfigureAwait(false))!;
379 }
380
381 /// <summary>
382 /// Gets the members of a conversation one page at a time.
383 /// </summary>
384 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
385 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
386 /// <param name="pageSize">Optional page size for the number of members to retrieve.</param>
387 /// <param name="continuationToken">Optional continuation token for pagination.</param>
388 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
389 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
390 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
391 /// <returns>A task that represents the asynchronous operation. The task result contains a page of members and an optional continuation token.</returns>
392 /// <exception cref="HttpRequestException">Thrown if the conversation members could not be retrieved successfully.</exception>
393 public virtual async Task<PagedMembersResult> GetConversationPagedMembersAsync(string conversationId, Uri serviceUrl, int? pageSize = null, string? continuationToken = null, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
394 {
395 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
396 ArgumentNullException.ThrowIfNull(serviceUrl);
397
398 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/pagedmembers";
399
400 List<string> queryParams = [];
401 if (pageSize.HasValue)
402 {
403 queryParams.Add($"pageSize={pageSize.Value}");
404 }
405 if (!string.IsNullOrWhiteSpace(continuationToken))
406 {
407 queryParams.Add($"continuationToken={Uri.EscapeDataString(continuationToken)}");
408 }
409 if (queryParams.Count > 0)
410 {
411 url += $"?{string.Join("&", queryParams)}";
412 }
413
414 return (await _botHttpClient.SendAsync<PagedMembersResult>(
415 HttpMethod.Get,
416 url,
417 body: null,
418 CreateRequestOptions(agenticIdentity, "getting paged conversation members", customHeaders),
419 cancellationToken).ConfigureAwait(false))!;
420 }
421
422 /// <summary>
423 /// Deletes a member from a conversation.
424 /// </summary>
425 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
426 /// <param name="memberId">The ID of the member to delete. Cannot be null or whitespace.</param>
427 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
428 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
429 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
430 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
431 /// <returns>A task that represents the asynchronous operation.</returns>
432 /// <exception cref="HttpRequestException">Thrown if the member could not be deleted successfully.</exception>
433 /// <remarks>If the deleted member was the last member of the conversation, the conversation is also deleted.</remarks>
434 public virtual async Task DeleteConversationMemberAsync(string conversationId, string memberId, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
435 {
436 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
437 ArgumentException.ThrowIfNullOrWhiteSpace(memberId);
438 ArgumentNullException.ThrowIfNull(serviceUrl);
439
440 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/members/{Uri.EscapeDataString(memberId)}";
441
442 await _botHttpClient.SendAsync(
443 HttpMethod.Delete,
444 url,
445 body: null,
446 CreateRequestOptions(agenticIdentity, "deleting conversation member", customHeaders),
447 cancellationToken).ConfigureAwait(false);
448 }
449
450 /// <summary>
451 /// Uploads and sends historic activities to the conversation.
452 /// </summary>
453 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
454 /// <param name="transcript">The transcript containing the historic activities. Cannot be null.</param>
455 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
456 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
457 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
458 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
459 /// <returns>A task that represents the asynchronous operation. The task result contains the response with a resource ID.</returns>
460 /// <exception cref="HttpRequestException">Thrown if the history could not be sent successfully.</exception>
461 /// <remarks>Activities in the transcript must have unique IDs and appropriate timestamps for proper rendering.</remarks>
462 public virtual async Task<SendConversationHistoryResponse> SendConversationHistoryAsync(string conversationId, Transcript transcript, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
463 {
464 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
465 ArgumentNullException.ThrowIfNull(transcript);
466 ArgumentNullException.ThrowIfNull(serviceUrl);
467
468 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/history";
469
470 string transcriptJson = JsonSerializer.Serialize(transcript, _jsonSerializerOptions);
471 logger.LogTraceGuarded("Sending conversation history to {Url}: {Transcript}", url, transcriptJson);
472
473 return (await _botHttpClient.SendAsync<SendConversationHistoryResponse>(
474 HttpMethod.Post,
475 url,
476 transcriptJson,
477 CreateRequestOptions(agenticIdentity, "sending conversation history", customHeaders),
478 cancellationToken).ConfigureAwait(false))!;
479 }
480
481 /// <summary>
482 /// Uploads an attachment to the channel's blob storage.
483 /// </summary>
484 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
485 /// <param name="attachmentData">The attachment data to upload. Cannot be null.</param>
486 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
487 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
488 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
489 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
490 /// <returns>A task that represents the asynchronous operation. The task result contains the response with an attachment ID.</returns>
491 /// <exception cref="HttpRequestException">Thrown if the attachment could not be uploaded successfully.</exception>
492 /// <remarks>This is useful for storing data in a compliant store when dealing with enterprises.</remarks>
493 public virtual async Task<UploadAttachmentResponse> UploadAttachmentAsync(string conversationId, AttachmentData attachmentData, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
494 {
495 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
496 ArgumentNullException.ThrowIfNull(attachmentData);
497 ArgumentNullException.ThrowIfNull(serviceUrl);
498
499 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/attachments";
500
501 string attachmentDataJson = JsonSerializer.Serialize(attachmentData, _jsonSerializerOptions);
502 logger.LogTraceGuarded("Uploading attachment to {Url}: {AttachmentData}", url, attachmentDataJson);
503
504 return (await _botHttpClient.SendAsync<UploadAttachmentResponse>(
505 HttpMethod.Post,
506 url,
507 attachmentDataJson,
508 CreateRequestOptions(agenticIdentity, "uploading attachment", customHeaders),
509 cancellationToken).ConfigureAwait(false))!;
510 }
511
512 /// <summary>
513 /// Adds a reaction to an activity in a conversation.
514 /// </summary>
515 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
516 /// <param name="activityId">The ID of the activity to react to. Cannot be null or whitespace.</param>
517 /// <param name="reactionType">The type of reaction to add (e.g., "like", "heart", "laugh"). Cannot be null or whitespace.</param>
518 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
519 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
520 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
521 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
522 /// <returns>A task that represents the asynchronous operation.</returns>
523 /// <exception cref="HttpRequestException">Thrown if the reaction could not be added successfully.</exception>
524 public async Task AddReactionAsync(string conversationId, string activityId, string reactionType, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
525 {
526 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
527 ArgumentException.ThrowIfNullOrWhiteSpace(activityId);
528 ArgumentException.ThrowIfNullOrWhiteSpace(reactionType);
529 ArgumentNullException.ThrowIfNull(serviceUrl);
530
531 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/{Uri.EscapeDataString(activityId)}/reactions/{Uri.EscapeDataString(reactionType)}";
532
533 await _botHttpClient.SendAsync(
534 HttpMethod.Put,
535 url,
536 body: null,
537 CreateRequestOptions(agenticIdentity, "adding reaction", customHeaders),
538 cancellationToken).ConfigureAwait(false);
539 }
540
541 /// <summary>
542 /// Removes a reaction from an activity in a conversation.
543 /// </summary>
544 /// <param name="conversationId">The ID of the conversation. Cannot be null or whitespace.</param>
545 /// <param name="activityId">The ID of the activity to remove the reaction from. Cannot be null or whitespace.</param>
546 /// <param name="reactionType">The type of reaction to remove (e.g., "like", "heart", "laugh"). Cannot be null or whitespace.</param>
547 /// <param name="serviceUrl">The service URL for the conversation. Cannot be null.</param>
548 /// <param name="agenticIdentity">Optional agentic identity for authentication.</param>
549 /// <param name="customHeaders">Optional custom headers to include in the request.</param>
550 /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
551 /// <returns>A task that represents the asynchronous operation.</returns>
552 /// <exception cref="HttpRequestException">Thrown if the reaction could not be removed successfully.</exception>
553 public async Task DeleteReactionAsync(string conversationId, string activityId, string reactionType, Uri serviceUrl, AgenticIdentity? agenticIdentity = null, CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
554 {
555 ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
556 ArgumentException.ThrowIfNullOrWhiteSpace(activityId);
557 ArgumentException.ThrowIfNullOrWhiteSpace(reactionType);
558 ArgumentNullException.ThrowIfNull(serviceUrl);
559
560 string url = $"{serviceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(conversationId)}/activities/{Uri.EscapeDataString(activityId)}/reactions/{Uri.EscapeDataString(reactionType)}";
561
562 await _botHttpClient.SendAsync(
563 HttpMethod.Delete,
564 url,
565 body: null,
566 CreateRequestOptions(agenticIdentity, "deleting reaction", customHeaders),
567 cancellationToken).ConfigureAwait(false);
568 }
569
570 private BotRequestOptions CreateRequestOptions(AgenticIdentity? agenticIdentity, string operationDescription, CustomHeaders? customHeaders) =>
571 new()
572 {
573 AgenticIdentity = agenticIdentity,
574 OperationDescription = operationDescription,
575 DefaultHeaders = DefaultCustomHeaders,
576 CustomHeaders = customHeaders
577 };
578}
579