Add agentic identity defaults to Teams app API#598
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces default agentic identity propagation through Context.Api and service URL-scoped ApiClient instances, and extends Teams Apps API clients (notably ActivityClient) to support default identity plus explicit per-call overrides. It also adds TeamsBotApplication.GetAgenticIdentity(...) with blueprint/tenant defaulting (blueprint defaults to AppId, tenant defaults from configured TeamsBotApplicationOptions.TenantId when available).
Changes:
- Add
TeamsBotApplicationOptions.TenantIdand propagate it from hosting configuration; use it as the default tenant when building agentic identities viaTeamsBotApplication.GetAgenticIdentity(...). - Add
ApiClient.ForServiceUrl(Uri, AgenticIdentity?)and plumb default request context through service-url scoped sub-clients viaApiRequestContext. - Add/expand tests validating default vs explicit agentic identity behavior across
Context.Api, scopedApiClient, andActivityClientoperations.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| core/test/Microsoft.Teams.Apps.UnitTests/TeamsBotApplicationHostingExtensionsTests.cs | Extends DI/hosting test coverage to assert TeamsBotApplicationOptions.TenantId is set from config. |
| core/test/Microsoft.Teams.Apps.UnitTests/AgenticIdentityDefaultsTests.cs | New unit tests validating default/override behavior for agentic identities across scoped clients and activity operations. |
| core/src/Microsoft.Teams.Apps/TeamsBotApplicationOptions.cs | Adds TenantId option used for defaulting agentic identity tenant in TeamsBotApplication.GetAgenticIdentity. |
| core/src/Microsoft.Teams.Apps/TeamsBotApplication.HostingExtensions.cs | Populates TeamsBotApplicationOptions.TenantId from BotConfig.TenantId (normalizing empty/whitespace to null). |
| core/src/Microsoft.Teams.Apps/TeamsBotApplication.cs | Caches configured tenant ID and adds GetAgenticIdentity(...); includes tenant stamping in proactive send “From” identity. |
| core/src/Microsoft.Teams.Apps/Context.cs | Scopes Context.Api by service URL plus default agentic identity derived from the inbound activity’s Recipient. |
| core/src/Microsoft.Teams.Apps/Api/Clients/TeamClient.cs | Threads default request context into request option creation via ApiRequestContext. |
| core/src/Microsoft.Teams.Apps/Api/Clients/ReactionClient.cs | Uses ApiRequestContext.Resolve(...) to apply default/explicit request context. |
| core/src/Microsoft.Teams.Apps/Api/Clients/MemberClient.cs | Uses ApiRequestContext.Resolve(...) to apply default/explicit request context. |
| core/src/Microsoft.Teams.Apps/Api/Clients/MeetingClient.cs | Threads default request context into request option creation via ApiRequestContext. |
| core/src/Microsoft.Teams.Apps/Api/Clients/ConversationApiClient.cs | Passes default request context through to Activities, Members, and Reactions; uses ApiRequestContext.Resolve(...) for create conversation. |
| core/src/Microsoft.Teams.Apps/Api/Clients/ApiRequestContext.cs | New helper to merge default request context with explicit agentic identity and/or activity-derived context. |
| core/src/Microsoft.Teams.Apps/Api/Clients/ApiClient.cs | Adds a service-url scoped client variant that carries a default agentic identity; plumbs default context into sub-clients. |
| core/src/Microsoft.Teams.Apps/Api/Clients/ActivityClient.cs | Adds identity-aware overloads and default identity support for activity create/update/reply/delete, targeted operations, and activity member lookup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// <summary> | ||
| /// Create a new activity in a conversation. | ||
| /// </summary> | ||
| public Task<SendActivityResponse?> CreateAsync(string conversationId, CoreActivity activity, AgenticIdentity? agenticIdentity, Dictionary<string, string>? additionalHeaders = null, CancellationToken cancellationToken = default) |
| /// <summary> | ||
| /// Update an existing activity in a conversation. | ||
| /// </summary> | ||
| public Task<UpdateActivityResponse> UpdateAsync(string conversationId, string id, CoreActivity activity, AgenticIdentity? agenticIdentity, Dictionary<string, string>? additionalHeaders = null, CancellationToken cancellationToken = default) |
| /// <summary> | ||
| /// Reply to an existing activity in a conversation. | ||
| /// </summary> | ||
| public Task<SendActivityResponse?> ReplyAsync(string conversationId, string id, CoreActivity activity, AgenticIdentity? agenticIdentity, Dictionary<string, string>? additionalHeaders = null, CancellationToken cancellationToken = default) |
| /// Targeted activities are only visible to the specified recipient. | ||
| /// </summary> | ||
| [Experimental("ExperimentalTeamsTargeted")] | ||
| public Task<SendActivityResponse?> CreateTargetedAsync(string conversationId, CoreActivity activity, AgenticIdentity? agenticIdentity, Dictionary<string, string>? additionalHeaders = null, CancellationToken cancellationToken = default) |
| /// Update an existing targeted activity in a conversation. | ||
| /// </summary> | ||
| [Experimental("ExperimentalTeamsTargeted")] | ||
| public Task<UpdateActivityResponse> UpdateTargetedAsync(string conversationId, string id, CoreActivity activity, AgenticIdentity? agenticIdentity, Dictionary<string, string>? additionalHeaders = null, CancellationToken cancellationToken = default) |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
0b34f84 to
e0ba479
Compare
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| /// <param name="tenantId">Optional tenant ID of the agent instance for multi-tenant applications. Overrides the configured tenant ID when provided.</param> | ||
| /// <param name="agenticAppBlueprintId">Optional agentic application blueprint ID. Defaults to this application's AppId.</param> | ||
| /// <returns>An <see cref="AgenticIdentity"/> populated with application defaults.</returns> | ||
| public AgenticIdentity GetAgenticIdentity(string agenticAppId, string agenticUserId, string? tenantId = null, string? agenticAppBlueprintId = null) |
There was a problem hiding this comment.
Is this method needed anywhere ?
| /// <param name="serviceUrl">The Bot Framework service URL for this scope.</param> | ||
| /// <param name="defaultAgenticIdentity">The default agentic identity for service URL-bound sub-clients.</param> | ||
| /// <returns>A new <see cref="ApiClient"/> bound to the given service URL and default identity.</returns> | ||
| public virtual ApiClient ForServiceUrl(Uri serviceUrl, AgenticIdentity? defaultAgenticIdentity) |
| ArgumentNullException.ThrowIfNull(activity); | ||
| activity.ServiceUrl ??= _serviceUrl; | ||
| return _client.UpdateTargetedActivityAsync(conversationId, id, activity, requestContext: BotRequestContext.FromActivity(activity), customHeaders: additionalHeaders, cancellationToken: cancellationToken); | ||
| BotRequestContext? requestContext = BotRequestContext.Merge( |
There was a problem hiding this comment.
add this merge to conversation client in core instead for consistency
| ArgumentNullException.ThrowIfNull(activity); | ||
| activity.ServiceUrl ??= _serviceUrl; | ||
| return _client.UpdateActivityAsync(conversationId, id, activity, requestContext: BotRequestContext.FromActivity(activity), customHeaders: additionalHeaders, cancellationToken: cancellationToken); | ||
| BotRequestContext? requestContext = BotRequestContext.Merge( |
There was a problem hiding this comment.
same, add merge to core client
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Summary
Context.Apiand service URL-scoped Apps API clients, while preserving explicit per-call identity overrides.RequestOptionsfor service URL and agentic identity so scoped clients and activity create/update/reply/member/targeted calls can use one extensible per-call options value.TeamsBotApplication.GetAgenticIdentity(...)with blueprint defaulting toAppIdand tenant defaulting fromTeamsBotApplicationOptionswhen configured.Breaking change
additionalHeadersparameter. This is intentional: the activity APIs now useRequestOptionsfor per-call service URL and identity and do not support custom additional headers on these methods.Tenant ID note
BotConfigfor auth/issuer/token plumbing, butTeamsBotApplicationOptionsdid not carry it because the Apps layer previously only needed the app ID.BotConfig.TenantIdinto Teams options soGetAgenticIdentity(...)can populateAgenticIdentity.TenantIdwithout requiring callers to pass the configured tenant every time.BotAuthenticationHandleror add tenant-aware token acquisition; whitespace/unconfigured tenant values remainnull.Validation
dotnet test core/test/Microsoft.Teams.Apps.UnitTests/Microsoft.Teams.Apps.UnitTests.csproj --no-restore