Repository: Azure/azure-sdk-for-net
Pull request #49315: [System.ClientModel] Support all properties needed to create a client in ClientCache and consolidate ClientConnection.Credential usage
Author: login: ShivangiReja
Labels: name: Azure.Core
name: OpenAI
Created: 2025-04-09T17:24:21Z | Merged: 2025-04-30T17:49:46Z

**Task 1:**  
Enhanced the `ClientCache` to support client reuse based on all necessary properties required to create a client instance.  
- Updated the internal cache key to account for differences in client properties including client options by adding an `IEquatable<object>` identifier.
- This ensures that multiple clients of the same type and same ID but with different option instances (e.g., `AzureOpenAIClientOptions`) are cached and retrieved independently.

Example:
```csharp
project.GetAzureOpenAIChatClient("a", options1);
project.GetAzureOpenAIChatClient("a", options2);
```
Both clients are now stored as distinct entries if `options1 != options2`.

- Included a targeted test case validating the correct behavior for distinct option instances.

**Task 2:**  
Simplified `ClientConnection` by removing the `ApiKeyCredential` property.  
- All authentication details are now routed through the unified `Credential` property.


Diff:
diff --git a/sdk/ai/Azure.AI.Inference/src/Customized/AIInferenceExtensions.cs b/sdk/ai/Azure.AI.Inference/src/Customized/AIInferenceExtensions.cs
index 2b84ad591a0c..ab9d699e7634 100644
--- a/sdk/ai/Azure.AI.Inference/src/Customized/AIInferenceExtensions.cs
+++ b/sdk/ai/Azure.AI.Inference/src/Customized/AIInferenceExtensions.cs
@@ -21,20 +21,26 @@ public static class AIInferenceExtensions
         /// <returns></returns>
         public static ChatCompletionsClient GetChatCompletionsClient(this ConnectionProvider provider)
         {
-            ChatCompletionsClient chatClient = provider.Subclients.GetClient(() => CreateChatCompletionsClient(provider), null);
+            ChatCompletionsClientKey chatCompletionsClientKey = new();
+            ChatCompletionsClient chatClient = provider.Subclients.GetClient(chatCompletionsClientKey, () => CreateChatCompletionsClient(provider));
             return chatClient;
         }
 
         private static ChatCompletionsClient CreateChatCompletionsClient(this ConnectionProvider provider)
         {
             ClientConnection connection = provider.GetConnection(typeof(ChatCompletionsClient).FullName!);
+
             if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
             {
                 throw new InvalidOperationException("Invalid URI.");
             }
-            return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new ChatCompletionsClient(uri, connection.Credential as TokenCredential)
-            : new ChatCompletionsClient(uri, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+            return connection.CredentialKind switch
+            {
+                CredentialKind.ApiKeyString => new ChatCompletionsClient(uri, new AzureKeyCredential((string)connection.Credential!)),
+                CredentialKind.TokenCredential => new ChatCompletionsClient(uri, (TokenCredential)connection.Credential!),
+                _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+            };
         }
 
         /// <summary>
@@ -44,20 +50,30 @@ private static ChatCompletionsClient CreateChatCompletionsClient(this Connection
         /// <returns></returns>
         public static EmbeddingsClient GetEmbeddingsClient(this ConnectionProvider provider)
         {
-            EmbeddingsClient embeddingsClient = provider.Subclients.GetClient(() => CreateEmbeddingsClient(provider), null);
+            EmbeddingsClientKey embeddingsClientKey = new();
+            EmbeddingsClient embeddingsClient = provider.Subclients.GetClient(embeddingsClientKey, () => CreateEmbeddingsClient(provider));
             return embeddingsClient;
         }
 
         private static EmbeddingsClient CreateEmbeddingsClient(this ConnectionProvider provider)
         {
             ClientConnection connection = provider.GetConnection(typeof(ChatCompletionsClient).FullName!);
+
             if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
             {
                 throw new InvalidOperationException("Invalid URI.");
             }
-            return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new EmbeddingsClient(uri, connection.Credential as TokenCredential)
-            : new EmbeddingsClient(uri, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+            return connection.CredentialKind switch
+            {
+                CredentialKind.ApiKeyString => new EmbeddingsClient(uri, new AzureKeyCredential((string)connection.Credential!)),
+                CredentialKind.TokenCredential => new EmbeddingsClient(uri, (TokenCredential)connection.Credential!),
+                _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+            };
         }
+
+        private record ChatCompletionsClientKey() : IEquatable<object>;
+
+        private record EmbeddingsClientKey() : IEquatable<object>;
     }
 }
diff --git a/sdk/ai/Azure.AI.Inference/src/Helpers/Polyfill/System.Runtime.CompilerServices.IsExternalInit.cs b/sdk/ai/Azure.AI.Inference/src/Helpers/Polyfill/System.Runtime.CompilerServices.IsExternalInit.cs
new file mode 100644
index 000000000000..b34c34cfa645
--- /dev/null
+++ b/sdk/ai/Azure.AI.Inference/src/Helpers/Polyfill/System.Runtime.CompilerServices.IsExternalInit.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#if !NET5_0_OR_GREATER
+
+using System.ComponentModel;
+namespace System.Runtime.CompilerServices;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+internal static class IsExternalInit { }
+
+#endif // !NET5_0_OR_GREATER
diff --git a/sdk/ai/Azure.AI.Projects/src/Azure.AI.Projects.csproj b/sdk/ai/Azure.AI.Projects/src/Azure.AI.Projects.csproj
index 1d13303d80cb..6a5d55aa0403 100644
--- a/sdk/ai/Azure.AI.Projects/src/Azure.AI.Projects.csproj
+++ b/sdk/ai/Azure.AI.Projects/src/Azure.AI.Projects.csproj
@@ -15,7 +15,10 @@
   <ItemGroup>
     <PackageReference Include="Azure.Core" />
     <PackageReference Include="Azure.Identity" />
-    <PackageReference Include="System.ClientModel" VersionOverride="1.4.0-beta.3" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\core\System.ClientModel\src\System.ClientModel.csproj" />
   </ItemGroup>
 
   <!-- Shared source from Azure.Core -->
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/ConnectionCacheManager.cs b/sdk/ai/Azure.AI.Projects/src/Custom/ConnectionCacheManager.cs
index d64a98894579..7d8943adc245 100644
--- a/sdk/ai/Azure.AI.Projects/src/Custom/ConnectionCacheManager.cs
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/ConnectionCacheManager.cs
@@ -52,7 +52,7 @@ public ClientConnection GetConnection(string connectionId)
                     throw new ArgumentException($"The API key is missing or invalid for {connectionId}.");
                 }
 
-                var newConnection = new ClientConnection(connectionId, apiKeyAuthProperties.Target, apiKeyAuthProperties.Credentials.Key);
+                var newConnection = new ClientConnection(connectionId, apiKeyAuthProperties.Target, apiKeyAuthProperties.Credentials.Key, CredentialKind.ApiKeyString);
                 return _connections.GetOrAdd(connectionId, newConnection);
             }
             else if (connection.Properties.AuthType == AuthenticationType.EntraId)
@@ -63,7 +63,7 @@ public ClientConnection GetConnection(string connectionId)
                     throw new ArgumentException($"The AAD authentication target URI is missing or invalid for {connectionId}.");
                 }
 
-                var newConnection = new ClientConnection(connectionId, aadAuthProperties.Target, _tokenCredential);
+                var newConnection = new ClientConnection(connectionId, aadAuthProperties.Target, _tokenCredential, CredentialKind.TokenCredential);
                 return _connections.GetOrAdd(connectionId, newConnection);
             }
 
diff --git a/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj b/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
index f5305c68d532..16b54e76d5a0 100644
--- a/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
+++ b/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
@@ -35,6 +35,7 @@
     <!--TODO: remove VersionOverride https://github.com/Azure/azure-sdk-for-net/issues/49730 -->
     <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
     <PackageReference Include="Azure.Search.Documents" VersionOverride="11.7.0-beta.3" />
+    <PackageReference Include="System.Text.Json" />
   </ItemGroup>
 
 </Project>
diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs
index 4f661a227319..266b52967e81 100644
--- a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs
+++ b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs
@@ -82,16 +82,10 @@ protected AsyncCollectionResult() { }
         public abstract System.ClientModel.ContinuationToken? GetContinuationToken(System.ClientModel.ClientResult page);
         public abstract System.Collections.Generic.IAsyncEnumerable<System.ClientModel.ClientResult> GetRawPagesAsync();
     }
-    public enum ClientAuthenticationMethod
-    {
-        Credential = 0,
-        ApiKey = 1,
-        NoAuth = 2,
-    }
     public partial class ClientCache
     {
         public ClientCache(int maxSize = 100) { }
-        public T GetClient<T>(System.Func<T> createClient, string? id) where T : class { throw null; }
+        public T GetClient<T>(System.IEquatable<object> clientId, System.Func<T> createClient) where T : class { throw null; }
     }
     [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
     public readonly partial struct ClientConnection
@@ -99,11 +93,9 @@ public readonly partial struct ClientConnection
         private readonly object _dummy;
         private readonly int _dummyPrimitive;
         public ClientConnection(string id, string locator) { throw null; }
-        public ClientConnection(string id, string locator, object credential) { throw null; }
-        public ClientConnection(string id, string locator, string apiKey) { throw null; }
-        public string? ApiKeyCredential { get { throw null; } }
-        public System.ClientModel.Primitives.ClientAuthenticationMethod Authentication { get { throw null; } }
+        public ClientConnection(string id, string locator, object credential, System.ClientModel.Primitives.CredentialKind credentialKind) { throw null; }
         public object? Credential { get { throw null; } }
+        public System.ClientModel.Primitives.CredentialKind CredentialKind { get { throw null; } }
         public string Id { get { throw null; } }
         public string Locator { get { throw null; } }
         public override string ToString() { throw null; }
@@ -188,6 +180,12 @@ protected ConnectionProvider(int maxCacheSize = 100) { }
         public abstract System.Collections.Generic.IEnumerable<System.ClientModel.Primitives.ClientConnection> GetAllConnections();
         public abstract System.ClientModel.Primitives.ClientConnection GetConnection(string connectionId);
     }
+    public enum CredentialKind
+    {
+        None = 0,
+        ApiKeyString = 1,
+        TokenCredential = 2,
+    }
     public partial class HttpClientPipelineTransport : System.ClientModel.Primitives.PipelineTransport, System.IDisposable
     {
         public HttpClientPipelineTransport() { }
diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs
index 4f661a227319..266b52967e81 100644
--- a/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs
+++ b/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs
@@ -82,16 +82,10 @@ protected AsyncCollectionResult() { }
         public abstract System.ClientModel.ContinuationToken? GetContinuationToken(System.ClientModel.ClientResult page);
         public abstract System.Collections.Generic.IAsyncEnumerable<System.ClientModel.ClientResult> GetRawPagesAsync();
     }
-    public enum ClientAuthenticationMethod
-    {
-        Credential = 0,
-        ApiKey = 1,
-        NoAuth = 2,
-    }
     public partial class ClientCache
     {
         public ClientCache(int maxSize = 100) { }
-        public T GetClient<T>(System.Func<T> createClient, string? id) where T : class { throw null; }
+        public T GetClient<T>(System.IEquatable<object> clientId, System.Func<T> createClient) where T : class { throw null; }
     }
     [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
     public readonly partial struct ClientConnection
@@ -99,11 +93,9 @@ public readonly partial struct ClientConnection
         private readonly object _dummy;
         private readonly int _dummyPrimitive;
         public ClientConnection(string id, string locator) { throw null; }
-        public ClientConnection(string id, string locator, object credential) { throw null; }
-        public ClientConnection(string id, string locator, string apiKey) { throw null; }
-        public string? ApiKeyCredential { get { throw null; } }
-        public System.ClientModel.Primitives.ClientAuthenticationMethod Authentication { get { throw null; } }
+        public ClientConnection(string id, string locator, object credential, System.ClientModel.Primitives.CredentialKind credentialKind) { throw null; }
         public object? Credential { get { throw null; } }
+        public System.ClientModel.Primitives.CredentialKind CredentialKind { get { throw null; } }
         public string Id { get { throw null; } }
         public string Locator { get { throw null; } }
         public override string ToString() { throw null; }
@@ -188,6 +180,12 @@ protected ConnectionProvider(int maxCacheSize = 100) { }
         public abstract System.Collections.Generic.IEnumerable<System.ClientModel.Primitives.ClientConnection> GetAllConnections();
         public abstract System.ClientModel.Primitives.ClientConnection GetConnection(string connectionId);
     }
+    public enum CredentialKind
+    {
+        None = 0,
+        ApiKeyString = 1,
+        TokenCredential = 2,
+    }
     public partial class HttpClientPipelineTransport : System.ClientModel.Primitives.PipelineTransport, System.IDisposable
     {
         public HttpClientPipelineTransport() { }
diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs
index aca1d3cdd92c..76cd1fc542f5 100644
--- a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs
+++ b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs
@@ -82,16 +82,10 @@ protected AsyncCollectionResult() { }
         public abstract System.ClientModel.ContinuationToken? GetContinuationToken(System.ClientModel.ClientResult page);
         public abstract System.Collections.Generic.IAsyncEnumerable<System.ClientModel.ClientResult> GetRawPagesAsync();
     }
-    public enum ClientAuthenticationMethod
-    {
-        Credential = 0,
-        ApiKey = 1,
-        NoAuth = 2,
-    }
     public partial class ClientCache
     {
         public ClientCache(int maxSize = 100) { }
-        public T GetClient<T>(System.Func<T> createClient, string? id) where T : class { throw null; }
+        public T GetClient<T>(System.IEquatable<object> clientId, System.Func<T> createClient) where T : class { throw null; }
     }
     [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
     public readonly partial struct ClientConnection
@@ -99,11 +93,9 @@ public readonly partial struct ClientConnection
         private readonly object _dummy;
         private readonly int _dummyPrimitive;
         public ClientConnection(string id, string locator) { throw null; }
-        public ClientConnection(string id, string locator, object credential) { throw null; }
-        public ClientConnection(string id, string locator, string apiKey) { throw null; }
-        public string? ApiKeyCredential { get { throw null; } }
-        public System.ClientModel.Primitives.ClientAuthenticationMethod Authentication { get { throw null; } }
+        public ClientConnection(string id, string locator, object credential, System.ClientModel.Primitives.CredentialKind credentialKind) { throw null; }
         public object? Credential { get { throw null; } }
+        public System.ClientModel.Primitives.CredentialKind CredentialKind { get { throw null; } }
         public string Id { get { throw null; } }
         public string Locator { get { throw null; } }
         public override string ToString() { throw null; }
@@ -188,6 +180,12 @@ protected ConnectionProvider(int maxCacheSize = 100) { }
         public abstract System.Collections.Generic.IEnumerable<System.ClientModel.Primitives.ClientConnection> GetAllConnections();
         public abstract System.ClientModel.Primitives.ClientConnection GetConnection(string connectionId);
     }
+    public enum CredentialKind
+    {
+        None = 0,
+        ApiKeyString = 1,
+        TokenCredential = 2,
+    }
     public partial class HttpClientPipelineTransport : System.ClientModel.Primitives.PipelineTransport, System.IDisposable
     {
         public HttpClientPipelineTransport() { }
diff --git a/sdk/core/System.ClientModel/src/Convenience/ClientCache.cs b/sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
index 3ab071fd37dd..c5eb7bc3d756 100644
--- a/sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
+++ b/sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
@@ -1,10 +1,7 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
-using System.Collections.Generic;
 using System.Diagnostics;
-using System.Linq;
-using System.Threading;
 
 namespace System.ClientModel.Primitives;
 
@@ -14,7 +11,7 @@ namespace System.ClientModel.Primitives;
 /// </summary>
 public class ClientCache
 {
-    private readonly Dictionary<(Type, string), ClientEntry> _clients = new();
+    private readonly Dictionary<IEquatable<object>, ClientEntry> _clients = new();
     private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);
 
     private readonly int _maxSize;
@@ -33,18 +30,22 @@ public ClientCache(int maxSize = 100)
     /// Updates the last-used timestamp on every hit.
     /// </summary>
     /// <typeparam name="T">The type of the client.</typeparam>
+    /// <param name="clientId">An equality-comparable key representing the client configuration.</param>
     /// <param name="createClient">A factory function to create the client if not cached.</param>
-    /// <param name="id">An identifier for the client instance.</param>
     /// <returns>The cached or newly created client instance.</returns>
-    public T GetClient<T>(Func<T> createClient, string? id) where T : class
+    public T GetClient<T>(IEquatable<object> clientId, Func<T> createClient) where T : class
     {
-        (Type, string) key = (typeof(T), id ?? string.Empty);
-
         // If the client exists, update its timestamp.
-        if (_clients.TryGetValue(key, out var cached))
+        if (_clients.TryGetValue(clientId, out var cached))
         {
             cached.LastUsed = Stopwatch.GetTimestamp();
-            return (T)cached.Client;
+
+            if (cached.Client is T typedClient)
+            {
+                return typedClient;
+            }
+
+            throw new InvalidOperationException($"The clientId is associated with a client of type '{cached.Client.GetType()}', not '{typeof(T)}'.");
         }
 
         // Client not found in cache, create a new one.
@@ -52,7 +53,7 @@ public T GetClient<T>(Func<T> createClient, string? id) where T : class
         try
         {
             T created = createClient();
-            _clients[key] = new ClientEntry(created, Stopwatch.GetTimestamp());
+            _clients[clientId] = new ClientEntry(created, Stopwatch.GetTimestamp());
 
             // After insertion, if cache exceeds the limit, perform cleanup.
             if (_clients.Count > _maxSize)
diff --git a/sdk/core/System.ClientModel/src/Convenience/ClientConnection.cs b/sdk/core/System.ClientModel/src/Convenience/ClientConnection.cs
index fade13d4fb6c..ca16b9c8a5cf 100644
--- a/sdk/core/System.ClientModel/src/Convenience/ClientConnection.cs
+++ b/sdk/core/System.ClientModel/src/Convenience/ClientConnection.cs
@@ -8,35 +8,14 @@ namespace System.ClientModel.Primitives;
 /// </summary>
 public readonly struct ClientConnection
 {
-    /// <summary>
-    /// Initializes a new instance of the <see cref="ClientConnection"/> struct with an API key credential.
-    /// </summary>
-    /// <param name="id">The identifier for the connection.</param>
-    /// <param name="locator">The endpoint or resource identifier.</param>
-    /// <param name="apiKey">The API key credential.</param>
-    public ClientConnection(string id, string locator, string apiKey)
-    {
-        if (string.IsNullOrWhiteSpace(id))
-            throw new ArgumentException("Id cannot be null or empty.", nameof(id));
-        if (string.IsNullOrWhiteSpace(locator))
-            throw new ArgumentException("Locator cannot be null or empty.", nameof(locator));
-        if (string.IsNullOrWhiteSpace(apiKey))
-            throw new ArgumentException("API Key cannot be null or empty.", nameof(apiKey));
-
-        Id = id;
-        Locator = locator;
-        Authentication = ClientAuthenticationMethod.ApiKey;
-        ApiKeyCredential = apiKey;
-        Credential = null;
-    }
-
     /// <summary>
     /// Initializes a new instance of the <see cref="ClientConnection"/> struct with a token credential.
     /// </summary>
     /// <param name="id">The identifier for the connection.</param>
     /// <param name="locator">The endpoint or resource identifier.</param>
-    /// <param name="credential">The token credential.</param>
-    public ClientConnection(string id, string locator, object credential)
+    /// <param name="credential">The client credential.</param>
+    /// <param name="credentialKind">The kind of connection used by the client.</param>
+    public ClientConnection(string id, string locator, object credential, CredentialKind credentialKind)
     {
         if (string.IsNullOrWhiteSpace(id))
             throw new ArgumentException("Id cannot be null or empty.", nameof(id));
@@ -47,9 +26,8 @@ public ClientConnection(string id, string locator, object credential)
 
         Id = id;
         Locator = locator;
-        Authentication = ClientAuthenticationMethod.Credential;
         Credential = credential;
-        ApiKeyCredential = null;
+        CredentialKind = credentialKind;
     }
 
     /// <summary>
@@ -66,28 +44,23 @@ public ClientConnection(string id, string locator)
 
         Id = id;
         Locator = locator;
-        Authentication = ClientAuthenticationMethod.NoAuth;
+        CredentialKind = CredentialKind.None;
     }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="ClientConnection"/> struct with the specified subclient ID.
     /// It is only for the JSON serializer.
     /// </summary>
-    /// <param name="id"></param>
-    /// <param name="locator">The subclient ID.</param>
-    /// <param name="auth"></param>
-    internal ClientConnection(string id, string locator, ClientAuthenticationMethod auth)
+    /// <param name="id">The identifier for the connection.</param>
+    /// <param name="locator">The endpoint or resource identifier.</param>
+    /// <param name="credentialKind">The kind of connection used by the client</param>
+    internal ClientConnection(string id, string locator, CredentialKind credentialKind)
     {
         Id = id;
         Locator = locator;
-        Authentication = auth;
+        CredentialKind = credentialKind;
     }
 
-    /// <summary>
-    /// Gets the kind of connection used by the client.
-    /// </summary>
-    public ClientAuthenticationMethod Authentication { get; }
-
     /// <summary>
     /// Gets the connection identifier.
     /// </summary>
@@ -99,15 +72,14 @@ internal ClientConnection(string id, string locator, ClientAuthenticationMethod
     public string Locator { get; }
 
     /// <summary>
-    /// Gets the API key credential, if applicable.
+    /// Gets the credential.
     /// </summary>
-    public string? ApiKeyCredential { get; }
+    public object? Credential { get; }
 
     /// <summary>
-    /// Gets the token credential, if applicable.
-    /// Since TokenCredential is not available in this package, using object type as a placeholder.
+    /// Gets the kind of connection used by the client.
     /// </summary>
-    public object? Credential { get; }
+    public CredentialKind CredentialKind { get; }
 
     /// <summary>
     /// Tries to convert the connection locator to a URI.
diff --git a/sdk/core/System.ClientModel/src/Convenience/ConnectionCollection.cs b/sdk/core/System.ClientModel/src/Convenience/ConnectionCollection.cs
index a63a468c3647..65450f9549f6 100644
--- a/sdk/core/System.ClientModel/src/Convenience/ConnectionCollection.cs
+++ b/sdk/core/System.ClientModel/src/Convenience/ConnectionCollection.cs
@@ -3,7 +3,6 @@
 
 #nullable disable
 
-using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.Text.Json;
@@ -67,14 +66,14 @@ public override ConnectionCollection Read(ref Utf8JsonReader reader, Type typeTo
 
         foreach (JsonElement connectionJson in json.EnumerateArray())
         {
-            // Retrieve 'id', 'locator', and 'auth' properties from the JSON
+            // Retrieve 'id', 'locator', and 'credentialKind' properties from the JSON
             string id = connectionJson.GetProperty("id").GetString();
             string locator = connectionJson.GetProperty("locator").GetString();
-            string auth = connectionJson.GetProperty("auth").GetString();
+            string credentialKind = connectionJson.GetProperty("credentialKind").GetString();
 
-            ClientAuthenticationMethod authMethod = (ClientAuthenticationMethod)Enum.Parse(typeof(ClientAuthenticationMethod), auth);
+            CredentialKind kind = (CredentialKind)Enum.Parse(typeof(CredentialKind), credentialKind);
 
-            ClientConnection connection = new ClientConnection(id, locator, authMethod);
+            ClientConnection connection = new ClientConnection(id, locator, kind);
             connections.Add(connection);
         }
 
@@ -89,7 +88,7 @@ public override void Write(Utf8JsonWriter writer, ConnectionCollection value, Js
             writer.WriteStartObject();
             writer.WriteString("id", connection.Id);
             writer.WriteString("locator", connection.Locator);
-            writer.WriteString("auth", connection.Authentication.ToString());
+            writer.WriteString("credentialKind", connection.CredentialKind.ToString());
             writer.WriteEndObject();
         }
         writer.WriteEndArray();
diff --git a/sdk/core/System.ClientModel/src/Convenience/ClientAuthenticationMethod.cs b/sdk/core/System.ClientModel/src/Convenience/CredentialKind.cs
similarity index 66%
rename from sdk/core/System.ClientModel/src/Convenience/ClientAuthenticationMethod.cs
rename to sdk/core/System.ClientModel/src/Convenience/CredentialKind.cs
index 3bf25b56b05d..80d40850f959 100644
--- a/sdk/core/System.ClientModel/src/Convenience/ClientAuthenticationMethod.cs
+++ b/sdk/core/System.ClientModel/src/Convenience/CredentialKind.cs
@@ -5,20 +5,20 @@ namespace System.ClientModel.Primitives;
 /// <summary>
 /// Specifies the kind of connection used by the client.
 /// </summary>
-public enum ClientAuthenticationMethod
+public enum CredentialKind
 {
     /// <summary>
-    /// Represents a connection using Credential.
+    /// Represents a connection using an out-of-band method.
     /// </summary>
-    Credential,
+    None = 0,
 
     /// <summary>
-    /// Represents a connection using an API key.
+    /// Represents a connection using an API key as a string.
     /// </summary>
-    ApiKey,
+    ApiKeyString = 1,
 
     /// <summary>
-    /// Represents a connection using an out-of-band method.
+    /// Represents a connection using Token Credential.
     /// </summary>
-    NoAuth
+    TokenCredential = 2,
 }
diff --git a/sdk/core/System.ClientModel/src/System.ClientModel.csproj b/sdk/core/System.ClientModel/src/System.ClientModel.csproj
index 61e1f8b5aebb..bcb230c3f7e8 100644
--- a/sdk/core/System.ClientModel/src/System.ClientModel.csproj
+++ b/sdk/core/System.ClientModel/src/System.ClientModel.csproj
@@ -9,6 +9,7 @@
     <TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
     <PackageIcon>DotNetPackageIcon.png</PackageIcon>
     <PackageIconPath>$(RepoEngPath)/images/$(PackageIcon)</PackageIconPath>
+    <ImplicitUsings>enable</ImplicitUsings>
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/sdk/core/System.ClientModel/tests/Convenience/ClientCacheTests.cs b/sdk/core/System.ClientModel/tests/Convenience/ClientCacheTests.cs
index 5a2855db382b..aedd699270b4 100644
--- a/sdk/core/System.ClientModel/tests/Convenience/ClientCacheTests.cs
+++ b/sdk/core/System.ClientModel/tests/Convenience/ClientCacheTests.cs
@@ -19,11 +19,11 @@ public void CacheShouldCleanupWhenExceedsLimit()
         for (int i = 0; i < 110; i++)
         {
             var client = new object();
-            clientCache.GetClient(() => client, $"client{i}");
+            clientCache.GetClient(new DummyClientKey($"client{i}"), () => client);
         }
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(clientCache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as Dictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "The _clients field is null.");
         Assert.AreEqual(100, clients!.Count, "Cache did not cleanup correctly.");
@@ -38,11 +38,11 @@ public void CacheShouldNotCleanupWhenUnderLimit()
         for (int i = 0; i < 50; i++)
         {
             var client = new object();
-            clientCache.GetClient(() => client, $"client{i}");
+            clientCache.GetClient(new DummyClientKey($"client{i}"), () => client);
         }
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(clientCache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as Dictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "The _clients field is null.");
         Assert.AreEqual(50, clients!.Count, "Cache should not have cleaned up when under the limit.");
@@ -57,31 +57,31 @@ public void CacheShouldCleanupOldestClients()
         for (int i = 0; i < 110; i++)
         {
             var client = new object();
-            clientCache.GetClient(() => client, $"client{i}");
+            clientCache.GetClient(new DummyClientKey($"client{i}"), () => client);
         }
 
         // Re-access some clients to mark them as recently used
         // (These keys will be reinserted and move to the head of the LRU list.)
-        clientCache.GetClient(() => new object(), "client0");
-        clientCache.GetClient(() => new object(), "client1");
+        clientCache.GetClient(new DummyClientKey("client0"), () => new object());
+        clientCache.GetClient(new DummyClientKey("client1"), () => new object());
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(clientCache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as Dictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "The _clients field is null.");
         Assert.AreEqual(100, clients!.Count, "Cache did not cleanup correctly.");
 
         // Ensure that recently accessed clients are still in the cache.
-        Assert.IsTrue(clients.ContainsKey((typeof(object), "client0")), "Most recently accessed client 'client0' should be in the cache.");
-        Assert.IsTrue(clients.ContainsKey((typeof(object), "client1")), "Most recently accessed client 'client1' should be in the cache.");
+        Assert.IsTrue(clients.ContainsKey(new DummyClientKey("client0")), "Most recently accessed client 'client0' should be in the cache.");
+        Assert.IsTrue(clients.ContainsKey(new DummyClientKey("client1")), "Most recently accessed client 'client1' should be in the cache.");
 
         // Based on the LRU policy, after adding 110 items then re-accessing "client0" and "client1",
         // the evicted keys are those that were least recently used.
         // Keys "client2" through "client11" should have been evicted.
         for (int i = 2; i < 12; i++)
         {
-            Assert.IsFalse(clients.ContainsKey((typeof(object), $"client{i}")),
-                $"Least recently used client 'client{i}' should have been removed.");
+            Assert.IsFalse(clients.ContainsKey(new DummyClientKey($"client{i}")),
+            $"Least recently used client 'client{i}' should have been removed.");
         }
     }
 
@@ -93,29 +93,29 @@ public void LRUShouldNotBeRemoved()
         for (int i = 0; i <= 100; i++)
         {
             var client = new object();
-            clientCache.GetClient(() => client, $"client{i}");
+            clientCache.GetClient(new DummyClientKey($"client{i}"), () => client);
         }
 
         // (These keys will be reinserted and move to the head of the LRU list.)
-        clientCache.GetClient(() => new object(), "client0");
-        clientCache.GetClient(() => new object(), "client1");
+        clientCache.GetClient(new DummyClientKey("client0"), () => new object());
+        clientCache.GetClient(new DummyClientKey("client1"), () => new object());
 
-        clientCache.GetClient(() => new object(), "client101");
-        clientCache.GetClient(() => new object(), "client102");
+        clientCache.GetClient(new DummyClientKey("client101"), () => new object());
+        clientCache.GetClient(new DummyClientKey("client102"), () => new object());
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(clientCache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as Dictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "The _clients field is null.");
         Assert.AreEqual(100, clients!.Count, "Cache did not cleanup correctly.");
 
         // Ensure that recently accessed clients are still in the cache.
-        Assert.IsTrue(clients.ContainsKey((typeof(object), "client0")), "Most recently accessed client 'client0' should be in the cache.");
-        Assert.IsTrue(clients.ContainsKey((typeof(object), "client1")), "Most recently accessed client 'client1' should be in the cache.");
+        Assert.IsTrue(clients.ContainsKey(new DummyClientKey("client0")), "Most recently accessed client 'client0' should be in the cache.");
+        Assert.IsTrue(clients.ContainsKey(new DummyClientKey("client1")), "Most recently accessed client 'client1' should be in the cache.");
 
         // Keys "client2" through "client3" should have been evicted.
-        Assert.IsFalse(clients.ContainsKey((typeof(object), $"client2")));
-        Assert.IsFalse(clients.ContainsKey((typeof(object), $"client3")));
+        Assert.IsFalse(clients.ContainsKey(new DummyClientKey("client2")));
+        Assert.IsFalse(clients.ContainsKey(new DummyClientKey("client3")));
     }
 
     [Test]
@@ -125,17 +125,17 @@ public void CacheShouldDisposeClientsWhenRemoved()
 
         // Create a disposable client
         var disposableClient = new DisposableClient();
-        clientCache.GetClient(() => disposableClient, "client0");
+        clientCache.GetClient(new DummyClientKey("client0"), () => disposableClient);
 
         // Add more clients to exceed the limit and trigger cleanup
         for (int i = 0; i < 110; i++)
         {
             var client = new object();
-            clientCache.GetClient(() => client, $"client{i}");
+            clientCache.GetClient(new DummyClientKey($"client{i}"), () => client);
         }
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(clientCache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as Dictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "The _clients field is null.");
         Assert.IsTrue(disposableClient.IsDisposed, "Disposable client was not disposed correctly.");
@@ -148,51 +148,56 @@ public void CacheShouldHandleDifferentClientIdsSeparately()
 
         // Add clients with the same type but different IDs
         var client1 = new object();
-        clientCache.GetClient(() => client1, "client1");
+        clientCache.GetClient(new DummyClientKey("client1"), () => client1);
 
         var client2 = new object();
-        clientCache.GetClient(() => client2, "client2");
+        clientCache.GetClient(new DummyClientKey("client2"), () => client2);
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(clientCache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as Dictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "The _clients field is null.");
-        Assert.IsTrue(clients!.ContainsKey((typeof(object), "client1")), "Client1 should be in the cache.");
-        Assert.IsTrue(clients!.ContainsKey((typeof(object), "client2")), "Client2 should be in the cache.");
+        Assert.IsTrue(clients!.ContainsKey(new DummyClientKey("client1")), "Client1 should be in the cache.");
+        Assert.IsTrue(clients!.ContainsKey(new DummyClientKey("client2")), "Client2 should be in the cache.");
     }
 
     [Test]
     public void ClientCacheShouldRespectMaxCacheSize()
     {
-        var cache = new ClientCache(maxSize: 3);
+        var clientCache = new ClientCache(maxSize: 3);
 
-        // Insert 3 clients
-        cache.GetClient(() => new DummyClient("A"), "A");
-        cache.GetClient(() => new DummyClient("B"), "B");
-        cache.GetClient(() => new DummyClient("C"), "C");
+        // Insert 3 clients with DummyClientKey as the identifier
+        clientCache.GetClient(new DummyClientKey("A"), () => new object());
+        clientCache.GetClient(new DummyClientKey("B"), () => new object());
+        clientCache.GetClient(new DummyClientKey("C"), () => new object());
 
         // Access client A to mark it as most recently used
-        cache.GetClient<DummyClient>(() => throw new Exception("Should not recreate A"), "A");
+        var wasRecreated = false;
+        clientCache.GetClient(new DummyClientKey("A"), () =>
+        {
+            wasRecreated = true;
+            return new object();
+        });
 
         // Add a new client D to trigger eviction
-        cache.GetClient(() => new DummyClient("D"), "D");
+        clientCache.GetClient(new DummyClientKey("D"), () => new object());
 
         // A should still be in the cache (was recently used), so this should not call the factory
-        var wasRecreated = false;
-        cache.GetClient(() =>
+        clientCache.GetClient(new DummyClientKey("A"), () =>
         {
             wasRecreated = true;
-            return new DummyClient("A");
-        }, "A");
+            return new object();  // This call should not trigger recreation for A
+        });
 
         Assert.False(wasRecreated, "Client A was unexpectedly recreated");
 
         var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
-        var clients = clientsField?.GetValue(cache) as Dictionary<(Type, string), ClientEntry>;
+        var clients = clientsField?.GetValue(clientCache) as IDictionary<IEquatable<object>, ClientEntry>;
 
         Assert.IsNotNull(clients, "_clients dictionary should not be null");
 
-        var keys = clients!.Keys.Select(k => k.Item2).ToList();
+        // Extract the keys correctly
+        var keys = clients!.Keys.Select(k => k is DummyClientKey DummyClientKey ? DummyClientKey.Key : null).ToList();
 
         // Verify that the cache contains the expected clients
         CollectionAssert.AreEquivalent(new[] { "A", "C", "D" }, keys, "Cache should contain A, C, and D");
@@ -200,18 +205,37 @@ public void ClientCacheShouldRespectMaxCacheSize()
         // Ensure B was evicted as it was the least recently used
         Assert.IsFalse(keys.Contains("B"), "Client B should have been evicted.");
     }
-}
 
-internal class DummyClient
-{
-    public string Id { get; }
-
-    public DummyClient(string id)
+    [Test]
+    public void CacheShouldHandleDifferentOptionsSeparately()
     {
-        Id = id;
+        var clientCache = new ClientCache();
+
+        // Define two different options as DummyClientKeys
+        var options1 = new ClientPipelineOptions() { EnableDistributedTracing = true };
+        var options2 = new ClientPipelineOptions() { EnableDistributedTracing = false };
+
+        // Create and retrieve a client with options1
+        clientCache.GetClient(new DummyClientKey("abc", options1), () => new object()); // Miss
+        var client1 = clientCache.GetClient(new DummyClientKey("abc", options1), () => new object()); // Hit ✅
+
+        // Create and retrieve a client with options2 (should be a separate entry)
+        var client2 = clientCache.GetClient(new DummyClientKey("abc", options2), () => new object()); // Miss ✅
+
+        // Assert that the two clients are distinct objects
+        Assert.AreNotSame(client1, client2, "Clients should be distinct when options are different.");
+
+        var clientsField = typeof(ClientCache).GetField("_clients", BindingFlags.NonPublic | BindingFlags.Instance);
+        var clients = clientsField?.GetValue(clientCache) as IDictionary<IEquatable<object>, ClientEntry>;
+
+        // Assert that both clients are in the cache with the expected keys
+        Assert.IsTrue(clients!.ContainsKey(new DummyClientKey("abc", options1)), "Client with options1 should be in the cache.");
+        Assert.IsTrue(clients!.ContainsKey(new DummyClientKey("abc", options2)), "Client with options2 should be in the cache.");
     }
 }
 
+internal record DummyClientKey(string Key, ClientPipelineOptions? options = null) : IEquatable<object>;
+
 // Helper class to simulate a disposable client
 internal class DisposableClient : IDisposable
 {
diff --git a/sdk/core/System.ClientModel/tests/Convenience/ConnectionCollectionTests.cs b/sdk/core/System.ClientModel/tests/Convenience/ConnectionCollectionTests.cs
index 607010a5a40c..1d5b5bf14804 100644
--- a/sdk/core/System.ClientModel/tests/Convenience/ConnectionCollectionTests.cs
+++ b/sdk/core/System.ClientModel/tests/Convenience/ConnectionCollectionTests.cs
@@ -18,21 +18,22 @@ public class ConnectionCollectionTests
     public void AddConnectionShouldStoreCorrectly()
     {
         var collection = new ConnectionCollection();
-        var connection = new ClientConnection("conn1", "https://example.com", "myAPIKey");
+        var connection = new ClientConnection("conn1", "https://example.com", "myAPIKey", CredentialKind.ApiKeyString);
 
         collection.Add(connection);
 
         Assert.AreEqual(1, collection.Count);
         Assert.AreEqual("conn1", collection["conn1"].Id);
         Assert.AreEqual("https://example.com", collection["conn1"].Locator);
-        Assert.AreEqual(ClientAuthenticationMethod.ApiKey, collection["conn1"].Authentication);
+        Assert.AreEqual("myAPIKey", collection["conn1"].Credential);
+        Assert.AreEqual(CredentialKind.ApiKeyString, collection["conn1"].CredentialKind);
     }
 
     [Test]
     public void SerializeShouldMatchExpectedJson()
     {
         var collection = new ConnectionCollection();
-        collection.Add(new ClientConnection("conn1", "https://example.com", "myAPIKey"));
+        collection.Add(new ClientConnection("conn1", "https://example.com", "myAPIKey", CredentialKind.ApiKeyString));
 
         string json = JsonSerializer.Serialize(collection);
 
@@ -41,27 +42,27 @@ public void SerializeShouldMatchExpectedJson()
 
         Assert.AreEqual("conn1", root.GetProperty("id").GetString());
         Assert.AreEqual("https://example.com", root.GetProperty("locator").GetString());
-        Assert.AreEqual("ApiKey", root.GetProperty("auth").GetString());
+        Assert.AreEqual("ApiKeyString", root.GetProperty("credentialKind").GetString());
     }
 
     [Test]
     public void DeserializeShouldRestoreObjects()
     {
-        string json = "[ { \"id\": \"conn1\", \"locator\": \"https://example.com\", \"auth\": \"ApiKey\" } ]";
+        string json = "[ { \"id\": \"conn1\", \"locator\": \"https://example.com\", \"credentialKind\": \"ApiKeyString\" } ]";
 
         ConnectionCollection? collection = JsonSerializer.Deserialize<ConnectionCollection>(json);
 
         Assert.AreEqual(1, collection!.Count);
         Assert.AreEqual("conn1", collection["conn1"].Id);
         Assert.AreEqual("https://example.com", collection["conn1"].Locator);
-        Assert.AreEqual(ClientAuthenticationMethod.ApiKey, collection["conn1"].Authentication);
+        Assert.AreEqual(CredentialKind.ApiKeyString, collection["conn1"].CredentialKind);
     }
 
     [Test]
     public void SerializeAndDeserializeShouldBeEqual()
     {
         var collection = new ConnectionCollection();
-        collection.Add(new ClientConnection("id1", "locator1", "myAPIKey"));
+        collection.Add(new ClientConnection("id1", "locator1", "myAPIKey", CredentialKind.ApiKeyString));
 
         string json = JsonSerializer.Serialize(collection);
         ConnectionCollection? deserializedCollection = JsonSerializer.Deserialize<ConnectionCollection>(json);
@@ -69,6 +70,6 @@ public void SerializeAndDeserializeShouldBeEqual()
         Assert.IsNotNull(deserializedCollection);
         Assert.AreEqual(1, deserializedCollection!.Count);
         Assert.AreEqual("locator1", deserializedCollection["id1"].Locator);
-        Assert.AreEqual(ClientAuthenticationMethod.ApiKey, deserializedCollection["id1"].Authentication);
+        Assert.AreEqual(CredentialKind.ApiKeyString, deserializedCollection["id1"].CredentialKind);
     }
 }
diff --git a/sdk/openai/Azure.AI.OpenAI/src/Azure.AI.OpenAI.csproj b/sdk/openai/Azure.AI.OpenAI/src/Azure.AI.OpenAI.csproj
index 1659d8b146fe..c5563418be7f 100644
--- a/sdk/openai/Azure.AI.OpenAI/src/Azure.AI.OpenAI.csproj
+++ b/sdk/openai/Azure.AI.OpenAI/src/Azure.AI.OpenAI.csproj
@@ -23,9 +23,14 @@
     </InternalsVisibleTo>
   </ItemGroup>
 
+  <!-- TEMP until Azure.Core is released with updated SCM version -->
   <ItemGroup>
     <ProjectReference Include="..\..\..\core\System.ClientModel\src\System.ClientModel.csproj" />
   </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="System.Text.Json" />
+  </ItemGroup>
+  <!-- ==============================================================-->
 
   <Choose>
     <When Condition="'$(GAServiceVersionLabel)' != ''">
diff --git a/sdk/openai/Azure.AI.OpenAI/src/Custom/AzureOpenAIExtensions.cs b/sdk/openai/Azure.AI.OpenAI/src/Custom/AzureOpenAIExtensions.cs
index 49c0e68cacdb..300555dd7ba9 100644
--- a/sdk/openai/Azure.AI.OpenAI/src/Custom/AzureOpenAIExtensions.cs
+++ b/sdk/openai/Azure.AI.OpenAI/src/Custom/AzureOpenAIExtensions.cs
@@ -22,11 +22,14 @@ public static class AzureOpenAIExtensions
     /// <returns></returns>
     public static ChatClient GetAzureOpenAIChatClient(this ConnectionProvider provider, string? deploymentName = null)
     {
-        ChatClient chatClient = provider.Subclients.GetClient(() =>
+        ChatClientKey chatClientKey = new(deploymentName);
+        AzureOpenAIClientKey openAIClientKey = new();
+
+        ChatClient chatClient = provider.Subclients.GetClient(chatClientKey, () =>
         {
-            AzureOpenAIClient aoaiClient = provider.Subclients.GetClient(() => CreateAzureOpenAIClient(provider), null);
+            AzureOpenAIClient aoaiClient = provider.Subclients.GetClient(openAIClientKey, () => CreateAzureOpenAIClient(provider));
             return provider.CreateChatClient(aoaiClient, deploymentName);
-        }, deploymentName);
+        });
 
         return chatClient;
     }
@@ -39,11 +42,14 @@ public static ChatClient GetAzureOpenAIChatClient(this ConnectionProvider provid
     /// <returns></returns>
     public static EmbeddingClient GetAzureOpenAIEmbeddingClient(this ConnectionProvider provider, string? deploymentName = null)
     {
-        EmbeddingClient embeddingClient = provider.Subclients.GetClient(() =>
+        EmbeddingClientKey embeddingClientKey = new(deploymentName);
+        AzureOpenAIClientKey openAIClientKey = new();
+
+        EmbeddingClient embeddingClient = provider.Subclients.GetClient(embeddingClientKey, () =>
         {
-            AzureOpenAIClient aoaiClient = provider.Subclients.GetClient(() => CreateAzureOpenAIClient(provider), null);
+            AzureOpenAIClient aoaiClient = provider.Subclients.GetClient(openAIClientKey , () => CreateAzureOpenAIClient(provider));
             return provider.CreateEmbeddingClient(aoaiClient, deploymentName);
-        }, deploymentName);
+        });
 
         return embeddingClient;
     }
@@ -57,9 +63,12 @@ private static AzureOpenAIClient CreateAzureOpenAIClient(this ConnectionProvider
             throw new InvalidOperationException("Invalid URI.");
         }
 
-        return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new AzureOpenAIClient(uri, connection.Credential as TokenCredential)
-            : new AzureOpenAIClient(uri, new ApiKeyCredential(connection.ApiKeyCredential!));
+        return connection.CredentialKind switch
+        {
+            CredentialKind.ApiKeyString => new AzureOpenAIClient(uri, new ApiKeyCredential((string)connection.Credential!)),
+            CredentialKind.TokenCredential => new AzureOpenAIClient(uri, (TokenCredential)connection.Credential!),
+            _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+        };
     }
 
     private static ChatClient CreateChatClient(this ConnectionProvider provider, AzureOpenAIClient client, string? deploymentName = null)
@@ -75,4 +84,10 @@ private static EmbeddingClient CreateEmbeddingClient(this ConnectionProvider pro
         EmbeddingClient embedding = client.GetEmbeddingClient(deploymentName ?? connection.Locator);
         return embedding;
     }
+
+    private record AzureOpenAIClientKey() : IEquatable<object>;
+
+    private record ChatClientKey(string? DeploymentName) : IEquatable<object>;
+
+    private record EmbeddingClientKey(string? DeploymentName) : IEquatable<object>;
 }
diff --git a/sdk/search/Azure.Search.Documents/src/SearchExtensions.cs b/sdk/search/Azure.Search.Documents/src/SearchExtensions.cs
index fbe7b7c7be79..787a26672dc3 100644
--- a/sdk/search/Azure.Search.Documents/src/SearchExtensions.cs
+++ b/sdk/search/Azure.Search.Documents/src/SearchExtensions.cs
@@ -23,20 +23,26 @@ public static class SearchExtensions
     /// <returns></returns>
     public static SearchClient GetSearchClient(this ConnectionProvider provider, string indexName)
     {
-        SearchClient searchClient = provider.Subclients.GetClient(() => CreateSearchClient(provider, indexName), indexName);
+        SearchClientKey searchClientKey = new(indexName);
+        SearchClient searchClient = provider.Subclients.GetClient(searchClientKey, () => CreateSearchClient(provider, indexName));
         return searchClient;
     }
 
     private static SearchClient CreateSearchClient(this ConnectionProvider provider, string indexName)
     {
         ClientConnection connection = provider.GetConnection(typeof(SearchClient).FullName!);
+
         if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
         {
             throw new InvalidOperationException("Invalid URI.");
         }
-        return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new SearchClient(uri, indexName, connection.Credential as TokenCredential)
-            : new SearchClient(uri, indexName, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+        return connection.CredentialKind switch
+        {
+            CredentialKind.ApiKeyString => new SearchClient(uri, indexName, new AzureKeyCredential((string)connection.Credential!)),
+            CredentialKind.TokenCredential => new SearchClient(uri, indexName, (TokenCredential)connection.Credential!),
+            _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+        };
     }
 
     /// <summary>
@@ -46,20 +52,26 @@ private static SearchClient CreateSearchClient(this ConnectionProvider provider,
     /// <returns></returns>
     public static SearchIndexClient GetSearchIndexClient(this ConnectionProvider provider)
     {
-        SearchIndexClient searchIndexClient = provider.Subclients.GetClient(() => CreateSearchIndexClient(provider), null);
+        SearchIndexClientKey searchIndexClientKey = new();
+        SearchIndexClient searchIndexClient = provider.Subclients.GetClient(searchIndexClientKey, () => CreateSearchIndexClient(provider));
         return searchIndexClient;
     }
 
     private static SearchIndexClient CreateSearchIndexClient(this ConnectionProvider provider)
     {
         ClientConnection connection = provider.GetConnection(typeof(SearchIndexClient).FullName!);
+
         if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
         {
             throw new InvalidOperationException("Invalid URI.");
         }
-        return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new SearchIndexClient(uri, connection.Credential as TokenCredential)
-            : new SearchIndexClient(uri, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+        return connection.CredentialKind switch
+        {
+            CredentialKind.ApiKeyString => new SearchIndexClient(uri, new AzureKeyCredential((string)connection.Credential!)),
+            CredentialKind.TokenCredential => new SearchIndexClient(uri, (TokenCredential)connection.Credential!),
+            _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+        };
     }
 
     /// <summary>
@@ -69,19 +81,31 @@ private static SearchIndexClient CreateSearchIndexClient(this ConnectionProvider
     /// <returns></returns>
     public static SearchIndexerClient GetSearchIndexerClient(this ConnectionProvider provider)
     {
-        SearchIndexerClient searchIndexerClient = provider.Subclients.GetClient(() => CreateSearchIndexerClient(provider), null);
+        SearchIndexerClientKey searchIndexerClientKey = new();
+        SearchIndexerClient searchIndexerClient = provider.Subclients.GetClient(searchIndexerClientKey, () => CreateSearchIndexerClient(provider));
         return searchIndexerClient;
     }
 
     private static SearchIndexerClient CreateSearchIndexerClient(this ConnectionProvider provider)
     {
         ClientConnection connection = provider.GetConnection(typeof(SearchIndexClient).FullName!);
+
         if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
         {
             throw new InvalidOperationException("Invalid URI.");
         }
-        return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new SearchIndexerClient(uri, connection.Credential as TokenCredential)
-            : new SearchIndexerClient(uri, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+        return connection.CredentialKind switch
+        {
+            CredentialKind.ApiKeyString => new SearchIndexerClient(uri, new AzureKeyCredential((string)connection.Credential!)),
+            CredentialKind.TokenCredential => new SearchIndexerClient(uri, (TokenCredential)connection.Credential!),
+            _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+        };
     }
+
+    private record SearchClientKey(string IndexName) : IEquatable<object>;
+
+    private record SearchIndexClientKey() : IEquatable<object>;
+
+    private record SearchIndexerClientKey() : IEquatable<object>;
 }
diff --git a/sdk/search/Azure.Search.Documents/src/Utilities/Polyfill/System.Runtime.CompilerServices.IsExternalInit.cs b/sdk/search/Azure.Search.Documents/src/Utilities/Polyfill/System.Runtime.CompilerServices.IsExternalInit.cs
new file mode 100644
index 000000000000..b34c34cfa645
--- /dev/null
+++ b/sdk/search/Azure.Search.Documents/src/Utilities/Polyfill/System.Runtime.CompilerServices.IsExternalInit.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#if !NET5_0_OR_GREATER
+
+using System.ComponentModel;
+namespace System.Runtime.CompilerServices;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+internal static class IsExternalInit { }
+
+#endif // !NET5_0_OR_GREATER


Reviews:
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T22:22:01Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T22:29:10Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T22:33:19Z
user: login: KrzysztofCwalina
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-29T22:34:08Z
user: login: ShivangiReja
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T22:49:14Z
user: login: nick863
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T22:51:56Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T00:41:25Z
user: login: nick863
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T07:28:23Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T16:06:46Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T16:47:54Z
user: login: ShivangiReja
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T16:59:49Z
user: login: nick863
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T17:17:00Z
user: login: ShivangiReja
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T17:33:36Z
user: login: nick863
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-30T17:41:04Z
user: login: KrzysztofCwalina
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-30T17:49:16Z

Review comments:
path: sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
user: login: KrzysztofCwalina
body: You can remove "using System.Linq" (and other usings)
created_at: 2025-04-28T18:36:53Z
author_association: MEMBER
diff_hunk: @@ -34,14 +34,14 @@ public ClientCache(int maxSize = 100)
     /// </summary>
     /// <typeparam name="T">The type of the client.</typeparam>
     /// <param name="createClient">A factory function to create the client if not cached.</param>
-    /// <param name="id">An identifier for the client instance.</param>
+    /// <param name="key">An equality-comparable key representing the client configuration.</param>
     /// <returns>The cached or newly created client instance.</returns>
-    public T GetClient<T>(Func<T> createClient, string? id) where T : class
+    public T GetClient<T>(Func<T> createClient, IEquatable<object>? key) where T : class
path: sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
user: login: KrzysztofCwalina
body: I would like to change the sihnature of this method to: 
```public T GetClient<T>(IEquatable<object> clientId, Func<T> createClient) where T : class```
created_at: 2025-04-28T22:10:27Z
author_association: MEMBER
diff_hunk: @@ -34,14 +34,14 @@ public ClientCache(int maxSize = 100)
     /// </summary>
     /// <typeparam name="T">The type of the client.</typeparam>
     /// <param name="createClient">A factory function to create the client if not cached.</param>
-    /// <param name="id">An identifier for the client instance.</param>
+    /// <param name="key">An equality-comparable key representing the client configuration.</param>
     /// <returns>The cached or newly created client instance.</returns>
-    public T GetClient<T>(Func<T> createClient, string? id) where T : class
+    public T GetClient<T>(Func<T> createClient, IEquatable<object>? key) where T : class
     {
-        (Type, string) key = (typeof(T), id ?? string.Empty);
+        var cacheKey = (typeof(T), key);
path: sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
user: login: KrzysztofCwalina
body: let's not wrap type and key into this tuple and instead just use the key to lookup the dictionary. Then simply fail is the value int he dictionary cannot be cast to T
created_at: 2025-04-28T22:11:34Z
author_association: MEMBER
diff_hunk: @@ -33,15 +30,13 @@ public ClientCache(int maxSize = 100)
     /// Updates the last-used timestamp on every hit.
     /// </summary>
     /// <typeparam name="T">The type of the client.</typeparam>
+    /// <param name="clientId">An equality-comparable key representing the client configuration.</param>
     /// <param name="createClient">A factory function to create the client if not cached.</param>
-    /// <param name="id">An identifier for the client instance.</param>
     /// <returns>The cached or newly created client instance.</returns>
-    public T GetClient<T>(Func<T> createClient, string? id) where T : class
+    public T GetClient<T>(IEquatable<object> clientId, Func<T> createClient) where T : class
     {
-        (Type, string) key = (typeof(T), id ?? string.Empty);
-
         // If the client exists, update its timestamp.
-        if (_clients.TryGetValue(key, out var cached))
+        if (_clients.TryGetValue(clientId, out var cached))
         {
             cached.LastUsed = Stopwatch.GetTimestamp();
             return (T)cached.Client;
path: sdk/core/System.ClientModel/src/Convenience/ClientCache.cs
user: login: KrzysztofCwalina
body: instead of just casting, can you check the type of the cached lient and if it's not T, throw an InvalidOperationException saying that the clientId is associated with client of type cached.Client.GetType and not T
created_at: 2025-04-29T22:29:10Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: KrzysztofCwalina
body: do we want this overload? I just wanted the client cache to support this scenario before we GA it, but I am not sure we want this extension overload. The extension method is for simple scenarios. If people want to instantiate a customized client, maybe they should use the client ctor directly? And we can always add this overload later.
created_at: 2025-04-29T22:33:19Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: ShivangiReja
body: @nick863 mentioned that to set the UserAgent, they need to add `AzureAIInferenceClientOptions` to the `GetChatCompletionsClient` method. I believe Python supports this as well.
created_at: 2025-04-29T22:49:14Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: nick863
body: Yes, that is correct, we need a possibility to set custom User agent so we need to have `AzureAIInferenceClientOptions` exposed.
created_at: 2025-04-29T22:51:55Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: KrzysztofCwalina
body: @nick863,  can't you call the client constructor directly? why do you need these extension method?
created_at: 2025-04-30T00:41:25Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: nick863
body: @dargilco and @trangevi do we have a hard requirement to return the Inference client from ProjectClient?
created_at: 2025-04-30T07:28:23Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: KrzysztofCwalina
body: Inference client will be "returned" off the project client. This has nothing to do with whether end users can pass client options or not to the extension method. Also, @nick863, why do you think end users will want to change the UA header?
created_at: 2025-04-30T16:06:46Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: KrzysztofCwalina
body: FYI. I had a side thread with @nick863. We agreed that the options parameter won't help with the telemetry the Project team wants.
created_at: 2025-04-30T16:47:54Z
author_association: MEMBER
diff_hunk: @@ -2,8 +2,8 @@ namespace Azure.AI.Inference
 {
     public static partial class AIInferenceExtensions
     {
-        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
-        public static Azure.AI.Inference.EmbeddingsClient GetEmbeddingsClient(this System.ClientModel.Primitives.ConnectionProvider provider) { throw null; }
+        public static Azure.AI.Inference.ChatCompletionsClient GetChatCompletionsClient(this System.ClientModel.Primitives.ConnectionProvider provider, Azure.AI.Inference.AzureAIInferenceClientOptions? options = null) { throw null; }
path: sdk/ai/Azure.AI.Inference/api/Azure.AI.Inference.netstandard2.0.cs
user: login: ShivangiReja
body: I've removed client options from all the extension methods across all packages, including Inference, OpenAI, and Search. If needed, we can always add it later.
created_at: 2025-04-30T16:59:48Z
author_association: MEMBER
diff_hunk: @@ -44,20 +50,30 @@ private static ChatCompletionsClient CreateChatCompletionsClient(this Connection
         /// <returns></returns>
         public static EmbeddingsClient GetEmbeddingsClient(this ConnectionProvider provider)
         {
-            EmbeddingsClient embeddingsClient = provider.Subclients.GetClient(() => CreateEmbeddingsClient(provider), null);
+            EmbeddingsClientKey embeddingsClientKey = new();
+            EmbeddingsClient embeddingsClient = provider.Subclients.GetClient(embeddingsClientKey, () => CreateEmbeddingsClient(provider));
             return embeddingsClient;
         }
 
         private static EmbeddingsClient CreateEmbeddingsClient(this ConnectionProvider provider)
         {
             ClientConnection connection = provider.GetConnection(typeof(ChatCompletionsClient).FullName!);
+
             if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
             {
                 throw new InvalidOperationException("Invalid URI.");
             }
-            return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new EmbeddingsClient(uri, connection.Credential as TokenCredential)
-            : new EmbeddingsClient(uri, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+            return connection.CredentialKind switch
+            {
+                CredentialKind.ApiKeyString => new EmbeddingsClient(uri, new AzureKeyCredential((string)connection.Credential!)),
+                CredentialKind.TokenCredential => new EmbeddingsClient(uri, (TokenCredential)connection.Credential!),
+                _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+            };
         }
+
+        private record ChatCompletionsClientKey() : IEquatable<object>;
path: sdk/ai/Azure.AI.Inference/src/Customized/AIInferenceExtensions.cs
user: login: nick863
body: Could we leave private record ChatCompletionsClientKey(AzureAIInferenceClientOptions? Options = null) : IEquatable<object>; here to use it in futire for the UserAgent?
created_at: 2025-04-30T17:16:55Z
author_association: MEMBER
diff_hunk: @@ -44,20 +50,30 @@ private static ChatCompletionsClient CreateChatCompletionsClient(this Connection
         /// <returns></returns>
         public static EmbeddingsClient GetEmbeddingsClient(this ConnectionProvider provider)
         {
-            EmbeddingsClient embeddingsClient = provider.Subclients.GetClient(() => CreateEmbeddingsClient(provider), null);
+            EmbeddingsClientKey embeddingsClientKey = new();
+            EmbeddingsClient embeddingsClient = provider.Subclients.GetClient(embeddingsClientKey, () => CreateEmbeddingsClient(provider));
             return embeddingsClient;
         }
 
         private static EmbeddingsClient CreateEmbeddingsClient(this ConnectionProvider provider)
         {
             ClientConnection connection = provider.GetConnection(typeof(ChatCompletionsClient).FullName!);
+
             if (!connection.TryGetLocatorAsUri(out Uri? uri) || uri is null)
             {
                 throw new InvalidOperationException("Invalid URI.");
             }
-            return connection.Authentication == ClientAuthenticationMethod.Credential
-            ? new EmbeddingsClient(uri, connection.Credential as TokenCredential)
-            : new EmbeddingsClient(uri, new AzureKeyCredential(connection.ApiKeyCredential!));
+
+            return connection.CredentialKind switch
+            {
+                CredentialKind.ApiKeyString => new EmbeddingsClient(uri, new AzureKeyCredential((string)connection.Credential!)),
+                CredentialKind.TokenCredential => new EmbeddingsClient(uri, (TokenCredential)connection.Credential!),
+                _ => throw new InvalidOperationException($"Unsupported credential kind: {connection.CredentialKind}")
+            };
         }
+
+        private record ChatCompletionsClientKey() : IEquatable<object>;
path: sdk/ai/Azure.AI.Inference/src/Customized/AIInferenceExtensions.cs
user: login: ShivangiReja
body: This PR is specifically for the changes in SCM. We can add it future PR.
created_at: 2025-04-30T17:33:36Z
author_association: MEMBER

PR comments:
user: login: azure-sdk
created_at: 2025-04-09T17:33:07Z
body: ## API Change Check

APIView identified API level changes in this PR and created the following API reviews

[Azure.AI.OpenAI](https://spa.apiview.dev/review/c2e06b19612a4b8690a3230e8a1cd596?activeApiRevisionId=5ee93b6f13d940b190e8a89b97e6627a)
[System.ClientModel](https://spa.apiview.dev/review/1b123e7a51d44ebe945f0212ee039c65?activeApiRevisionId=50dff5253ff8482ea85ba12cb51804f1)
[Azure.Search.Documents](https://spa.apiview.dev/review/ce93b4e0d50e46c18296e720843a4651?activeApiRevisionId=093efb2aab6349a98ecbdb589c7708dc)
[Azure.AI.Inference](https://spa.apiview.dev/review/52190bf4171e4164a9eec8f5f51d137d?activeApiRevisionId=d2a1d0996bca4e6694e5c03954cefb49)
[Azure.AI.Projects](https://spa.apiview.dev/review/45fcc1c2ff9b4a609fa40bab9b308034?activeApiRevisionId=673d86e715f44cdf8e1dd4d4baf36e39)
<!-- Fetch URI: https://apiview.dev/api/pullrequests?pullRequestNumber=49315&repoName=Azure/azure-sdk-for-net&commitSHA=bc305be07e87867245c0d910a872d2c2f13e432f -->
author_association: COLLABORATOR
user: login: KrzysztofCwalina
created_at: 2025-04-28T22:21:39Z
body: It would be good if ClientConnection.Credential was typed as AuthenticationTokenProvider and not Object.
But I am not sure if we have time to coordinate. Maybe we just rename ClientConnection.Credential to ClientConnection.TokenProvider?


@christothes 
author_association: MEMBER
user: login: KrzysztofCwalina
created_at: 2025-04-28T22:32:46Z
body: @ShivangiReja , @christothes, should we remove ClientConnection.ApiKeyCredential and use the ClientConnection.Credential property for API key case? 
author_association: MEMBER
user: login: azure-sdk
created_at: 2025-04-29T20:42:38Z
body: **API change check**

APIView has identified API level changes in this PR and created following API reviews.

[Azure.AI.OpenAI](https://apiview.dev/Assemblies/Review/c2e06b19612a4b8690a3230e8a1cd596?revisionId=5ee93b6f13d940b190e8a89b97e6627a)
[System.ClientModel](https://apiview.dev/Assemblies/Review/1b123e7a51d44ebe945f0212ee039c65?revisionId=50dff5253ff8482ea85ba12cb51804f1)
[Azure.Search.Documents](https://apiview.dev/Assemblies/Review/ce93b4e0d50e46c18296e720843a4651?revisionId=093efb2aab6349a98ecbdb589c7708dc)
[Azure.AI.Inference](https://apiview.dev/Assemblies/Review/52190bf4171e4164a9eec8f5f51d137d?revisionId=d2a1d0996bca4e6694e5c03954cefb49)
[Azure.AI.Projects](https://apiview.dev/Assemblies/Review/45fcc1c2ff9b4a609fa40bab9b308034?revisionId=673d86e715f44cdf8e1dd4d4baf36e39)


author_association: COLLABORATOR