Skip to content

.NET: Set ApplicationName on CosmosClientOptions for UserAgent telemetry#6481

Open
TheovanKraay wants to merge 2 commits into
microsoft:mainfrom
TheovanKraay:fix/cosmos-useragent-application-name
Open

.NET: Set ApplicationName on CosmosClientOptions for UserAgent telemetry#6481
TheovanKraay wants to merge 2 commits into
microsoft:mainfrom
TheovanKraay:fix/cosmos-useragent-application-name

Conversation

@TheovanKraay

@TheovanKraay TheovanKraay commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Added CosmosOptionsHelper that sets CosmosClientOptions.ApplicationName per component, producing wire-visible UserAgent suffixes:

  • CosmosChatHistoryProvider: Microsoft.Agents.AI.CosmosNoSql.ChatHistory/{version}
  • CosmosCheckpointStore: Microsoft.Agents.AI.CosmosNoSql.Checkpoint/{version}

This ensures Cosmos DB requests from the Agent Framework are identifiable in telemetry, enabling usage tracking and diagnostics queries that can distinguish between chat history and checkpoint workloads.

When users provide their own CosmosClient instance, the ApplicationName is not overridden - users retain full control.

Motivation and Context

Description

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Copilot AI review requested due to automatic review settings June 11, 2026 18:42
@moonbox3 moonbox3 added the .NET label Jun 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a shared helper to standardize Cosmos DB client options across Agent Framework Cosmos NoSQL integrations, primarily to set a consistent CosmosClientOptions.ApplicationName for telemetry/diagnostics.

Changes:

  • Introduced CosmosOptionsHelper to build pre-configured CosmosClientOptions with an app name including component + library version.
  • Updated CosmosCheckpointStore and CosmosChatHistoryProvider to use the shared options helper when creating CosmosClient.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosOptionsHelper.cs New helper to centralize Cosmos client options and embed versioned ApplicationName.
dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosCheckpointStore.cs Uses helper-created options and preserves serializer configuration.
dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatHistoryProvider.cs Ensures internally-created Cosmos clients use helper-created options.

Comment thread dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosOptionsHelper.cs
Comment thread dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosOptionsHelper.cs Outdated
Comment thread dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosOptionsHelper.cs Outdated
@github-actions github-actions Bot changed the title Set ApplicationName on CosmosClientOptions for UserAgent telemetry .NET: Set ApplicationName on CosmosClientOptions for UserAgent telemetry Jun 11, 2026
@TheovanKraay TheovanKraay force-pushed the fix/cosmos-useragent-application-name branch from f517430 to 7da63da Compare June 11, 2026 18:53
@TheovanKraay TheovanKraay force-pushed the fix/cosmos-useragent-application-name branch from 7da63da to b29afbf Compare June 11, 2026 19:08
/// <returns>A new <see cref="CosmosClientOptions"/> with <see cref="CosmosClientOptions.ApplicationName"/> set.</returns>
public static CosmosClientOptions CreateOptions(string component)
{
var applicationName = $"Microsoft.Agents.CosmosNoSql.{component}/{s_version}";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to match the namespace and class name exactly:

Suggested change
var applicationName = $"Microsoft.Agents.CosmosNoSql.{component}/{s_version}";
var applicationName = $"Microsoft.Agents.AI.CosmosNoSql.{component}/{s_version}";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The application name now uses the full namespace prefix: Microsoft.Agents.AI.CosmosNoSql.{ClassName}/{version}, and both callers pass their actual class names (CosmosChatHistoryProvider and CosmosCheckpointStore).

Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputRequestMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputResponseMessageFilter = null)
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString)), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString), CosmosOptionsHelper.CreateOptions("ChatHistory")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to match the namespace and class name exactly. Same for the other locations and for CosmosCheckpointStore?

Suggested change
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString), CosmosOptionsHelper.CreateOptions("ChatHistory")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString), CosmosOptionsHelper.CreateOptions("CosmosChatHistoryProvider")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated both. The connection-string constructors now pass "CosmosChatHistoryProvider" and "CosmosCheckpointStore" respectively.

this._sessionState = new ProviderSessionState<State>(
Throw.IfNull(stateInitializer),
stateKey ?? this.GetType().Name);
this._cosmosClient = Throw.IfNull(cosmosClient);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to also set here, e.g.

if (string.IsNullOrWhitespace(this._cosmosClient.ClientOptions.ApplicationName))
{
    this._cosmosClient.ClientOptions.ApplicationName = ...
}

From what I can tell it looks like all this is settable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, both user-provided-client constructors now call CosmosOptionsHelper.EnsureApplicationName(cosmosClient, component) which sets ApplicationName only if it's null/empty. Users who already set their own ApplicationName won't be overridden.

/// Ensures all internally-created <see cref="CosmosClient"/> instances carry a consistent
/// <see cref="CosmosClientOptions.ApplicationName"/> for telemetry and diagnostics.
/// </summary>
internal static class CosmosOptionsHelper

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to add a couple of unit tests for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added CosmosOptionsHelperTests with 5 tests covering: application name format, different components produce different names, max-length truncation (64 chars), version extraction, and options creation. All passing.

@TheovanKraay TheovanKraay force-pushed the fix/cosmos-useragent-application-name branch from b29afbf to d27619a Compare June 12, 2026 11:05
Added CosmosOptionsHelper (in Microsoft.Agents.AI.CosmosNoSql namespace)
that sets CosmosClientOptions.ApplicationName per component, producing
wire-visible UserAgent suffixes:

- CosmosChatHistoryProvider: Microsoft.Agents.CosmosNoSql.ChatHistory/{version}
- CosmosCheckpointStore: Microsoft.Agents.CosmosNoSql.Checkpoint/{version}

This ensures Cosmos DB requests from the Agent Framework are identifiable
in telemetry, enabling usage tracking and diagnostics queries that can
distinguish between chat history and checkpoint workloads.

Addressed review feedback:
- Truncates ApplicationName to 64 chars (Cosmos SDK max length)
- Moved helper to Microsoft.Agents.AI.CosmosNoSql namespace (scoped ownership)
- Uses StringComparison.Ordinal for IndexOf call

When users provide their own CosmosClient instance, the ApplicationName
is not overridden - users retain full control.
…ojects

The MessagePack 2.5.192 package (GHSA-hv8m-jj95-wg3x) is a transitive
dependency pulled in by Aspire hosting packages. This is not controllable
by this repo until the Aspire packages release an update.

Suppresses NU1903 only in the affected Aspire-dependent projects.
@TheovanKraay

TheovanKraay commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Please note: build was failing due to NU1903 (known vulnerability) for MessagePack 2.5.192 - a transitive dependency pulled in by the Aspire hosting packages (Aspire.Hosting.AppHost, Aspire.Azure.AI.OpenAI, etc.) and is not controllable from this repo until those packages release an update. To unblock CI, I've suppressed NU1903 in the 5 affected Aspire-dependent projects only:

  • Aspire.Hosting.AgentFramework.DevUI
  • Aspire.Hosting.AgentFramework.DevUI.UnitTests
  • AgentWebChat.AppHost
  • AgentWebChat.AgentHost
  • DevUIIntegration.AppHost

Done in a separate commit, If preferred I can revert once the upstream Aspire packages update their MessagePack dependency. See: GHSA-hv8m-jj95-wg3x

public CosmosCheckpointStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId)
{
var cosmosClientOptions = new CosmosClientOptions
var cosmosClientOptions = CosmosOptionsHelper.CreateOptions("CosmosCheckpointStore");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var cosmosClientOptions = CosmosOptionsHelper.CreateOptions("CosmosCheckpointStore");
var cosmosClientOptions = CosmosOptionsHelper.CreateOptions(nameof(CosmosCheckpointStore));

public CosmosCheckpointStore(string connectionString, string databaseId, string containerId)
{
var cosmosClientOptions = new CosmosClientOptions();
var cosmosClientOptions = CosmosOptionsHelper.CreateOptions("CosmosCheckpointStore");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var cosmosClientOptions = CosmosOptionsHelper.CreateOptions("CosmosCheckpointStore");
var cosmosClientOptions = CosmosOptionsHelper.CreateOptions(nameof(CosmosCheckpointStore));

Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputRequestMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputResponseMessageFilter = null)
: this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential)), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
: this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential), CosmosOptionsHelper.CreateOptions("CosmosChatHistoryProvider")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
: this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential), CosmosOptionsHelper.CreateOptions("CosmosChatHistoryProvider")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
: this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential), CosmosOptionsHelper.CreateOptions(nameof(CosmosChatHistoryProvider))), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)

Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputRequestMessageFilter = null,
Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? storeInputResponseMessageFilter = null)
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString)), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString), CosmosOptionsHelper.CreateOptions("CosmosChatHistoryProvider")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString), CosmosOptionsHelper.CreateOptions("CosmosChatHistoryProvider")), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)
: this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString), CosmosOptionsHelper.CreateOptions(nameof(CosmosChatHistoryProvider))), databaseId, containerId, stateInitializer, ownsClient: true, stateKey, provideOutputMessageFilter, storeInputRequestMessageFilter, storeInputResponseMessageFilter)

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);NU1903</NoWarn>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A fix for this is already queued, so we should be able to revert this before merging.

See #6497

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants