microsoft/teams.net

Public

mirrored from https://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.Core/ConversationClient.cs

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