microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/update-release-process

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Apps/Diagnostics/TeamsBaggageBuilder.cs

170lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Globalization;
5using Microsoft.Teams.Apps.Schema;
6using OpenTelemetry;
7
8namespace Microsoft.Teams.Apps.Diagnostics;
9
10/// <summary>
11/// Builds OpenTelemetry baggage for Agent365 export from <c>Microsoft.Teams.Apps</c> turn types.
12/// </summary>
13/// <remarks>
14/// <para>
15/// Populates the cert-required keys (<c>microsoft.tenant.id</c>, <c>gen_ai.conversation.id</c>,
16/// <c>microsoft.channel.name</c>, etc.) plus the Apps-only keys backed by
17/// <see cref="TeamsChannelAccount"/> (<c>user.id</c>, <c>user.email</c>,
18/// <c>microsoft.agent.user.email</c>, <c>gen_ai.agent.description</c>).
19/// </para>
20/// </remarks>
21public sealed class TeamsBaggageBuilder
22{
23 private readonly Dictionary<string, string?> _pairs = new(StringComparer.Ordinal);
24
25 /// <summary>Sets the Microsoft Entra tenant id (<c>microsoft.tenant.id</c>). Required for cert.</summary>
26 public TeamsBaggageBuilder TenantId(string? v) => Set(AgentObservabilityKeys.TenantId, v);
27
28 /// <summary>Sets the conversation id (<c>gen_ai.conversation.id</c>). Required for cert.</summary>
29 public TeamsBaggageBuilder ConversationId(string? v) => Set(AgentObservabilityKeys.ConversationId, v);
30
31 /// <summary>Sets the conversation item link (<c>microsoft.conversation.item.link</c>). Optional.</summary>
32 public TeamsBaggageBuilder ConversationItemLink(string? v) => Set(AgentObservabilityKeys.ConversationItemLink, v);
33
34 /// <summary>Sets the channel name (<c>microsoft.channel.name</c>). Required for cert.</summary>
35 public TeamsBaggageBuilder ChannelName(string? v) => Set(AgentObservabilityKeys.ChannelName, v);
36
37 /// <summary>Sets the channel link (<c>microsoft.channel.link</c>). Optional. Not auto-populated by
38 /// <see cref="FromTeamsContext"/> — Teams's flat <c>ChannelId</c> string has no SubChannel concept.</summary>
39 public TeamsBaggageBuilder ChannelLink(string? v) => Set(AgentObservabilityKeys.ChannelLink, v);
40
41 /// <summary>Sets the agent id (<c>gen_ai.agent.id</c>). Required for cert.</summary>
42 public TeamsBaggageBuilder AgentId(string? v) => Set(AgentObservabilityKeys.AgentId, v);
43
44 /// <summary>Sets the agent display name (<c>gen_ai.agent.name</c>). Required for cert.</summary>
45 public TeamsBaggageBuilder AgentName(string? v) => Set(AgentObservabilityKeys.AgentName, v);
46
47 /// <summary>Sets the agentic user id (<c>microsoft.agent.user.id</c>). Required for cert.</summary>
48 public TeamsBaggageBuilder AgenticUserId(string? v) => Set(AgentObservabilityKeys.AgenticUserId, v);
49
50 /// <summary>Sets the agent blueprint id (<c>microsoft.a365.agent.blueprint.id</c>). Required for cert.</summary>
51 public TeamsBaggageBuilder AgentBlueprintId(string? v) => Set(AgentObservabilityKeys.AgentBlueprintId, v);
52
53 /// <summary>Sets the human user name (<c>user.name</c>). Optional.</summary>
54 public TeamsBaggageBuilder UserName(string? v) => Set(AgentObservabilityKeys.UserName, v);
55
56 /// <summary>Sets the operation source (<c>service.name</c>). Required for cert on server spans.</summary>
57 public TeamsBaggageBuilder OperationSource(string source) => Set(AgentObservabilityKeys.ServiceName, source);
58
59 /// <summary>Sets the InvokeAgent server address and (optional) port. Required for InvokeAgentScope cert.
60 /// The port is recorded only when different from the HTTPS default (443).</summary>
61 public TeamsBaggageBuilder InvokeAgentServer(string? address, int? port = null)
62 {
63 Set(AgentObservabilityKeys.ServerAddress, address);
64 if (port.HasValue && port.Value != 443)
65 {
66 Set(AgentObservabilityKeys.ServerPort, port.Value.ToString(CultureInfo.InvariantCulture));
67 }
68 return this;
69 }
70
71 /// <summary>Sets the human user id (<c>user.id</c>). Required for cert. Apps-only — backed by
72 /// <see cref="TeamsChannelAccount.AadObjectId"/>.</summary>
73 public TeamsBaggageBuilder UserId(string? v) => Set(AgentObservabilityKeys.UserId, v);
74
75 /// <summary>Sets the human user email (<c>user.email</c>). Required for cert. Apps-only.</summary>
76 public TeamsBaggageBuilder UserEmail(string? v) => Set(AgentObservabilityKeys.UserEmail, v);
77
78 /// <summary>Sets the agent description (<c>gen_ai.agent.description</c>). Optional. Apps-only —
79 /// backed by <see cref="TeamsChannelAccount.UserRole"/>.</summary>
80 public TeamsBaggageBuilder AgentDescription(string? v) => Set(AgentObservabilityKeys.AgentDescription, v);
81
82 /// <summary>Sets the agentic user email (<c>microsoft.agent.user.email</c>). Required for cert. Apps-only.</summary>
83 public TeamsBaggageBuilder AgenticUserEmail(string? v) => Set(AgentObservabilityKeys.AgenticUserEmail, v);
84
85 /// <summary>Escape hatch for setting any baggage key not exposed by a typed setter
86 /// (e.g. <c>client.address</c> derived in HTTP middleware).</summary>
87 public TeamsBaggageBuilder Set(string key, string? value)
88 {
89 if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value))
90 {
91 _pairs[key] = value;
92 }
93 return this;
94 }
95
96 /// <summary>
97 /// Populates every baggage key reachable from <c>ctx.Activity</c> — including the Apps-only keys
98 /// backed by <see cref="TeamsChannelAccount"/>. Tenant fallback uses the typed
99 /// <see cref="TeamsChannelData"/> when <see cref="TeamsChannelAccount.TenantId"/> is null.
100 /// </summary>
101 public TeamsBaggageBuilder FromTeamsContext<TActivity>(Context<TActivity> ctx) where TActivity : TeamsActivity
102 {
103 ArgumentNullException.ThrowIfNull(ctx);
104 TActivity activity = ctx.Activity;
105
106 ConversationId(activity.Conversation?.Id);
107 ConversationItemLink(activity.ServiceUrl?.ToString());
108 ChannelName(activity.ChannelId);
109
110 UserName(activity.From?.Name);
111if (activity.From is TeamsChannelAccount fromAccount)
112{
113 UserId(fromAccount.AadObjectId);
114 UserEmail(fromAccount.Email);
115}
116
117 TeamsChannelAccount? recipient = activity.Recipient;
118 if (recipient is not null)
119 {
120 AgentId(string.IsNullOrWhiteSpace(recipient.AgenticAppId) ? recipient.Id : recipient.AgenticAppId);
121 AgentName(recipient.Name);
122 AgenticUserId(recipient.AgenticUserId);
123 AgentBlueprintId(recipient.AgenticAppBlueprintId);
124 TenantId(recipient.TenantId);
125 AgenticUserEmail(recipient.Email);
126 AgentDescription(recipient.UserRole);
127 }
128
129 // Tenant fallback: typed channelData on TeamsActivity (no JSON parse needed).
130 if (!_pairs.ContainsKey(AgentObservabilityKeys.TenantId))
131 {
132 string? channelTenantId = activity.ChannelData?.Tenant?.Id;
133 if (!string.IsNullOrWhiteSpace(channelTenantId))
134 {
135 TenantId(channelTenantId);
136 }
137 }
138
139 return this;
140 }
141
142 /// <summary>
143 /// Applies the collected pairs to <see cref="Baggage.Current"/> and returns an
144 /// <see cref="IDisposable"/> that restores the previous baggage when disposed.
145 /// </summary>
146 public IDisposable Build()
147 {
148 Baggage previous = Baggage.Current;
149 foreach (KeyValuePair<string, string?> kvp in _pairs)
150 {
151 Baggage.Current = Baggage.Current.SetBaggage(kvp.Key, kvp.Value);
152 }
153 return new RestoreScope(previous);
154 }
155
156 private sealed class RestoreScope(Baggage previous) : IDisposable
157 {
158 private bool _disposed;
159
160 public void Dispose()
161 {
162 if (_disposed)
163 {
164 return;
165 }
166 Baggage.Current = previous;
167 _disposed = true;
168 }
169 }
170}
171