microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feature/pabot-httpcontext-botid

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/samples/A2ABot/A2A/A2AServer.cs

85lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5using A2A;
6using Microsoft.Extensions.AI;
7using Microsoft.Extensions.Logging;
8using Microsoft.Teams.Apps.Schema;
9using Microsoft.Teams.Core;
10using Microsoft.Teams.Core.Schema;
11
12namespace A2ABot.A2A;
13
14// Inbound A2A. Parses the DataPart into a HandoffMessage, creates a 1:1
15// Teams conversation with the user, asks Agent to seed that conversation's
16// session with the handoff context + greeting, then sends the greeting as
17// a proactive message.
18sealed class A2AServer(
19 Config config,
20 Agent agent,
21 ConversationClient conversations,
22 ILogger<A2AServer> logger) : IAgentHandler
23{
24 private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true };
25
26 public async Task ExecuteAsync(RequestContext context, AgentEventQueue eventQueue, CancellationToken ct)
27 {
28 MessageResponder responder = new(eventQueue, context.ContextId);
29
30 Part? dataPart = context.Message?.Parts?.FirstOrDefault(p => p.ContentCase == PartContentCase.Data);
31 if (dataPart?.Data is not { } data)
32 {
33 await responder.ReplyAsync("Expected a DataPart in the message.", ct);
34 return;
35 }
36
37 HandoffMessage? handoff = data.Deserialize<HandoffMessage>(JsonOpts);
38 logger.LogInformation(
39 "[{Bot}/A2A] received handoff: from={From} user={User} aadId={AadId} tenant={TenantId} serviceUrl={ServiceUrl}",
40 config.Name, handoff?.From, handoff?.UserName, handoff?.AadObjectId, handoff?.TenantId, handoff?.ServiceUrl);
41
42 if (handoff is null
43 || handoff.Kind != "handoff"
44 || string.IsNullOrEmpty(handoff.AadObjectId)
45 || string.IsNullOrEmpty(handoff.TenantId)
46 || string.IsNullOrEmpty(handoff.ServiceUrl))
47 {
48 await responder.ReplyAsync("Unsupported or incomplete handoff message.", ct);
49 return;
50 }
51
52 Uri serviceUrl = new(handoff.ServiceUrl);
53
54 CreateConversationResponse conv = await conversations.CreateConversationAsync(
55 new ConversationParameters
56 {
57 IsGroup = false,
58 TenantId = handoff.TenantId,
59 Members = [new TeamsConversationAccount { Id = handoff.AadObjectId }],
60 },
61 serviceUrl,
62 cancellationToken: ct);
63
64 string newConvId = conv.Id
65 ?? throw new InvalidOperationException("CreateConversation returned no Id.");
66
67 // Run the LLM with the handoff context so the greeting actually
68 // answers the question that came in the summary. The LLM's turn is
69 // stored in the thread, so subsequent user replies continue naturally.
70 string greeting = await agent.GreetWithHandoffAsync(
71 newConvId, handoff.From, handoff.UserName, handoff.Summary, ct);
72
73 TeamsActivity proactive = TeamsActivity.CreateBuilder()
74 .WithType(TeamsActivityType.Message)
75 .WithText(greeting)
76 .WithServiceUrl(serviceUrl)
77 .WithConversation(new TeamsConversation { Id = newConvId })
78 .Build();
79 SendActivityResponse? sent = await conversations.SendActivityAsync(proactive, cancellationToken: ct);
80 logger.LogInformation("[{Bot}/A2A] proactive greeting sent (conv={ConvId}, activityId={ActivityId})",
81 config.Name, newConvId, sent?.Id ?? "<none>");
82
83 await responder.ReplyAsync($"Handoff received and {handoff.UserName} contacted directly.", ct);
84 }
85}
86