microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feature/oauthflow-fixes

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/samples/A2ABot/A2A/A2AServer.cs

82lines · modecode

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