.NET: Set ApplicationName on CosmosClientOptions for UserAgent telemetry#6481
.NET: Set ApplicationName on CosmosClientOptions for UserAgent telemetry#6481TheovanKraay wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
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
CosmosOptionsHelperto build pre-configuredCosmosClientOptionswith an app name including component + library version. - Updated
CosmosCheckpointStoreandCosmosChatHistoryProviderto use the shared options helper when creatingCosmosClient.
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. |
f517430 to
7da63da
Compare
7da63da to
b29afbf
Compare
| /// <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}"; |
There was a problem hiding this comment.
Would it make sense to match the namespace and class name exactly:
| var applicationName = $"Microsoft.Agents.CosmosNoSql.{component}/{s_version}"; | |
| var applicationName = $"Microsoft.Agents.AI.CosmosNoSql.{component}/{s_version}"; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Would it make sense to match the namespace and class name exactly. Same for the other locations and for CosmosCheckpointStore?
| : 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) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Would be nice to add a couple of unit tests for this.
There was a problem hiding this comment.
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.
b29afbf to
d27619a
Compare
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.
d27619a to
70e978e
Compare
…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.
|
Please note: build was failing due to
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"); |
There was a problem hiding this comment.
| 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"); |
There was a problem hiding this comment.
| 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) |
There was a problem hiding this comment.
| : 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) |
There was a problem hiding this comment.
| : 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> |
There was a problem hiding this comment.
A fix for this is already queued, so we should be able to revert this before merging.
See #6497
Added CosmosOptionsHelper that sets CosmosClientOptions.ApplicationName per component, producing wire-visible UserAgent suffixes:
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