microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/a365-mcp

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

587lines · modecode

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