Repository: microsoft/kiota (csharp)

Issue:
Support auth reference id OpenAPI extension
### Is your feature request related to a problem? Please describe the problem.

Kiota does not recognize the OpenAPI extension for authentication vault reference id within the `securityScheme` object in OAS files. This value is required in the runtime object entry for copilot api plugins.

### Client library/SDK language

None

### Describe the solution you'd like

Kiota needs to support reading the `x-ai-auth-reference-id`  OpenAPI extension in OAS files and writing to the `runtime` object of the plugin manifest.

Sample OAS file:

```yaml
paths:
  /users:
    get:
      operationId: getUsers
      parameters: []
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
      security:
        - GraphOAuth2AuthAppOnly:
            - user.read.all
  /users/{id}:
    get:
      operationId: getUser
      parameters:
        - name: id
          in: path
          required: true
          description: The user id
          schema:
            type: string
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
      security:
        - GraphOAuth2AuthDelegated:
            - user.read
security:
  - GraphOAuth2AuthDelegated:
      - user.read
components:
  schemas:
    User:
      type: object
      required:
        - id
        - displayName
      properties:
        id:
          type: string
        displayName:
          type: string
  securitySchemes:
    GraphOAuth2AuthDelegated:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://login.microsoftonline.com/tenantid/oauth2/v2.0/authorize
          tokenUrl: https://login.microsoftonline.com/tenantid/oauth2/v2.0/token
          scopes:
            user.read: Grants read access to the signed-in user's profile
      x-ai-auth-reference-id-: 'someValue789'
    GraphOAuth2AuthAppOnly:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://login.microsoftonline.com/tenantid/oauth2/v2.0/token
          scopes:
            user.read.all: Grants read access to all users' full profiles
      x-ai-auth-reference-id-: 'otherValue123'
```

Expected plugin manifest:

```json
"functions": [
    {
      "name": "getUser"
    },
    {
      "name": "getUsers"
    }
  ],
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "OAuthPluginVault",
        "reference_id": "someValue789"
      },
      "spec": {
        "url": "msgraphusers-openapi.yml"
      },
      "run_for_functions": [
        "getUser"
      ]
    },
    {
      "type": "OpenApi",
      "auth": {
        "type": "OAuthPluginVault",
        "reference_id": "otherValue123"
      },
      "spec": {
        "url": "msgraphusers-openapi.yml"
      },
      "run_for_functions": [
        "getUsers"
      ]
    }
  ]
```

### Additional context

_No response_

Pull request:
closes #6196

Patch:
diff --git a/src/Kiota.Builder/Extensions/OpenApiSettingsExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiSettingsExtensions.cs
index 6546b601cb..a8c984b28e 100644
--- a/src/Kiota.Builder/Extensions/OpenApiSettingsExtensions.cs
+++ b/src/Kiota.Builder/Extensions/OpenApiSettingsExtensions.cs
@@ -19,6 +19,7 @@ public static void AddPluginsExtensions(this OpenApiReaderSettings settings)
         settings.ExtensionParsers.TryAdd(OpenApiLegalInfoUrlExtension.Name, static (i, _) => OpenApiLegalInfoUrlExtension.Parse(i));
         settings.ExtensionParsers.TryAdd(OpenApiAiReasoningInstructionsExtension.Name, static (i, _) => OpenApiAiReasoningInstructionsExtension.Parse(i));
         settings.ExtensionParsers.TryAdd(OpenApiAiRespondingInstructionsExtension.Name, static (i, _) => OpenApiAiRespondingInstructionsExtension.Parse(i));
+        settings.ExtensionParsers.TryAdd(OpenApiAiAuthReferenceIdExtension.Name, static (i, _) => OpenApiAiAuthReferenceIdExtension.Parse(i));
     }
 
     public static void AddGenerationExtensions(this OpenApiReaderSettings settings)
diff --git a/src/Kiota.Builder/OpenApiExtensions/OpenAPIAiAuthReferenceIdExtension.cs b/src/Kiota.Builder/OpenApiExtensions/OpenAPIAiAuthReferenceIdExtension.cs
new file mode 100644
index 0000000000..4e3c89479d
--- /dev/null
+++ b/src/Kiota.Builder/OpenApiExtensions/OpenAPIAiAuthReferenceIdExtension.cs
@@ -0,0 +1,20 @@
+﻿using System.Text.Json.Nodes;
+
+namespace Kiota.Builder.OpenApiExtensions;
+
+public class OpenApiAiAuthReferenceIdExtension : OpenApiSimpleStringExtension
+{
+    public static string Name => "x-ai-auth-reference-id";
+    public string? AuthenticationReferenceId
+    {
+        get; set;
+    }
+    protected override string? ValueSelector => AuthenticationReferenceId;
+    public static OpenApiAiAuthReferenceIdExtension Parse(JsonNode source)
+    {
+        return new OpenApiAiAuthReferenceIdExtension
+        {
+            AuthenticationReferenceId = ParseString(source)
+        };
+    }
+}
diff --git a/src/Kiota.Builder/Plugins/PluginsGenerationService.cs b/src/Kiota.Builder/Plugins/PluginsGenerationService.cs
index 32b566a07b..bc8ba71132 100644
--- a/src/Kiota.Builder/Plugins/PluginsGenerationService.cs
+++ b/src/Kiota.Builder/Plugins/PluginsGenerationService.cs
@@ -531,22 +531,27 @@ private static Auth GetAuth(IList<OpenApiSecurityRequirement> securityRequiremen
     private static Auth GetAuthFromSecurityScheme(OpenApiSecuritySchemeReference securityScheme)
     {
         string name = securityScheme.Reference.Id;
+        string? authenticationReferenceId = null;
+
+        if (securityScheme.Extensions.TryGetValue(OpenApiAiAuthReferenceIdExtension.Name, out var authReferenceIdExtension) && authReferenceIdExtension is OpenApiAiAuthReferenceIdExtension authReferenceId)
+            authenticationReferenceId = authReferenceId.AuthenticationReferenceId;
+
         return securityScheme.Type switch
         {
             SecuritySchemeType.ApiKey => new ApiKeyPluginVault
             {
-                ReferenceId = $"{{{name}_REGISTRATION_ID}}"
+                ReferenceId = string.IsNullOrEmpty(authenticationReferenceId) ? $"{{{name}_REGISTRATION_ID}}" : authenticationReferenceId
             },
             // Only Http bearer is supported
             SecuritySchemeType.Http when securityScheme.Scheme.Equals("bearer", StringComparison.OrdinalIgnoreCase) =>
                 new ApiKeyPluginVault { ReferenceId = $"{{{name}_REGISTRATION_ID}}" },
             SecuritySchemeType.OpenIdConnect => new ApiKeyPluginVault
             {
-                ReferenceId = $"{{{name}_REGISTRATION_ID}}"
+                ReferenceId = string.IsNullOrEmpty(authenticationReferenceId) ? $"{{{name}_REGISTRATION_ID}}" : authenticationReferenceId
             },
             SecuritySchemeType.OAuth2 => new OAuthPluginVault
             {
-                ReferenceId = $"{{{name}_CONFIGURATION_ID}}"
+                ReferenceId = string.IsNullOrEmpty(authenticationReferenceId) ? $"{{{name}_CONFIGURATION_ID}}" : authenticationReferenceId
             },
             _ => throw new UnsupportedSecuritySchemeException(["Bearer Token", "Api Key", "OpenId Connect", "OAuth"],
                 $"Unsupported security scheme type '{securityScheme.Type}'.")


Test patch:
diff --git a/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiAuthReferenceIdExtensionTests.cs b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiAuthReferenceIdExtensionTests.cs
new file mode 100644
index 0000000000..b941fef14f
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/OpenApiExtensions/OpenApiAiAuthReferenceIdExtensionTests.cs
@@ -0,0 +1,143 @@
+﻿using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Kiota.Builder.Configuration;
+using Kiota.Builder.OpenApiExtensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.OpenApi;
+using Microsoft.OpenApi.Writers;
+using Moq;
+using Xunit;
+
+namespace Kiota.Builder.Tests.OpenApiExtensions;
+public sealed class OpenApiAiReasoningInstructionsExtensionTest : IDisposable
+{
+    private readonly HttpClient _httpClient = new();
+    public void Dispose()
+    {
+        _httpClient.Dispose();
+    }
+    [Fact]
+    public void Parses()
+    {
+        var value = OpenApiAiAuthReferenceIdExtension.Parse("12345");
+        Assert.NotNull(value);
+        Assert.Equal("12345", value.AuthenticationReferenceId);
+    }
+    private readonly string TempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+    [Fact]
+    public async Task ParsesInDocumentAsync()
+    {
+        var documentContent = @"openapi: 3.0.0
+info:
+  title: Graph Users
+  version: 0.0.0
+servers:
+  - url: https://graph.microsoft.com/v1.0
+    description: The Microsoft Graph API
+tags: []
+paths:
+  /users:
+    get:
+      operationId: getUsers
+      parameters: []
+      responses:
+        '200':
+          description: The request has succeeded.
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/User'
+      security:
+        - GraphOAuth2AuthAppOnly:
+            - user.read.all
+  /users/{id}:
+    get:
+      operationId: getUser
+      parameters:
+        - name: id
+          in: path
+          required: true
+          description: The user id
+          schema:
+            type: string
+      responses:
+        '200':
+          description: The request has succeeded.
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/User'
+      security:
+        - GraphOAuth2AuthDelegated:
+            - user.read
+security:
+  - GraphOAuth2AuthDelegated:
+      - user.read
+components:
+  schemas:
+    User:
+      type: object
+      required:
+        - id
+        - displayName
+      properties:
+        id:
+          type: string
+        displayName:
+          type: string
+  securitySchemes:
+    GraphOAuth2AuthDelegated:
+      type: oauth2
+      flows:
+        authorizationCode:
+          authorizationUrl: https://login.microsoftonline.com/tenantid/oauth2/v2.0/authorize
+          tokenUrl: https://login.microsoftonline.com/tenantid/oauth2/v2.0/token
+          scopes:
+            user.read: Grants read access to the signed-in user's profile
+      x-ai-auth-reference-id: someValue789
+    GraphOAuth2AuthAppOnly:
+      type: oauth2
+      flows:
+        clientCredentials:
+          tokenUrl: https://login.microsoftonline.com/tenantid/oauth2/v2.0/token
+          scopes:
+            user.read.all: Grants read access to all users' full profiles
+      x-ai-auth-reference-id: 'otherValue123'";
+
+        Directory.CreateDirectory(TempDirectory);
+        var documentPath = Path.Combine(TempDirectory, "document.yaml");
+        await File.WriteAllTextAsync(documentPath, documentContent);
+        var mockLogger = new Mock<ILogger<OpenApiDescriptionForModelExtension>>();
+        var documentDownloadService = new OpenApiDocumentDownloadService(_httpClient, mockLogger.Object);
+        var generationConfig = new GenerationConfiguration { OutputPath = TempDirectory, PluginTypes = [PluginType.APIPlugin] };
+        var (openApiDocumentStream, _) = await documentDownloadService.LoadStreamAsync(documentPath, generationConfig);
+        var document = await documentDownloadService.GetDocumentFromStreamAsync(openApiDocumentStream, generationConfig);
+        Assert.NotNull(document);
+        Assert.NotEmpty(document.Components.SecuritySchemes);
+        Assert.Equal(2, document.Components.SecuritySchemes.Count);
+        var firstSecurityScheme = document.Components.SecuritySchemes.First().Value;
+        Assert.True(firstSecurityScheme.Extensions.TryGetValue(OpenApiAiAuthReferenceIdExtension.Name, out var authReferenceIdExtension));
+        Assert.IsType<OpenApiAiAuthReferenceIdExtension>(authReferenceIdExtension);
+        Assert.Equal("someValue789", ((OpenApiAiAuthReferenceIdExtension)authReferenceIdExtension).AuthenticationReferenceId);
+    }
+    [Fact]
+    public void Serializes()
+    {
+        var value = new OpenApiAiAuthReferenceIdExtension
+        {
+            AuthenticationReferenceId = "refid123",
+        };
+        using var sWriter = new StringWriter();
+        OpenApiJsonWriter writer = new(sWriter, new OpenApiJsonWriterSettings { Terse = true });
+
+
+        value.Write(writer, OpenApiSpecVersion.OpenApi3_0);
+        var result = sWriter.ToString();
+        Assert.Equal("\"refid123\"", result);
+    }
+}
diff --git a/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs b/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
index 9ec63d9e78..8a435ec803 100644
--- a/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
+++ b/tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
@@ -327,7 +327,7 @@ public static TheoryData<string, string, string, PluginAuthConfiguration, Action
             },
             // multiple security schemes
             {
-                "{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key0, in: header }, apiKey1: {type: apiKey, name: x-api-key1, in: header }}}",
+                "{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key0, in: header, x-ai-auth-reference-id: auth1234 }, apiKey1: {type: apiKey, name: x-api-key1, in: header }}}",
                 string.Empty, "security: [apiKey0: []]", null, resultingManifest =>
                 {
                     Assert.NotNull(resultingManifest.Document);
@@ -336,7 +336,7 @@ public static TheoryData<string, string, string, PluginAuthConfiguration, Action
                     var auth0 = resultingManifest.Document.Runtimes[0].Auth;
                     Assert.IsType<ApiKeyPluginVault>(auth0);
                     Assert.Equal(AuthType.ApiKeyPluginVault, auth0?.Type);
-                    Assert.Equal("{apiKey0_REGISTRATION_ID}", ((ApiKeyPluginVault)auth0!).ReferenceId);
+                    Assert.Equal("auth1234", ((ApiKeyPluginVault)auth0!).ReferenceId);
                 }
             },
             // security requirement in root object
@@ -356,7 +356,7 @@ public static TheoryData<string, string, string, PluginAuthConfiguration, Action
             // },
             // auth provided in config overrides openapi file auth
             {
-                "{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key, in: header }}}",
+                "{securitySchemes: {apiKey0: {type: apiKey, name: x-api-key, in: header, x-ai-auth-reference-id: auth1234 }}}",
                 string.Empty, "security: [apiKey0: []]", new PluginAuthConfiguration("different_ref_id") {AuthType = PluginAuthType.OAuthPluginVault}, resultingManifest =>
                 {
                     Assert.NotNull(resultingManifest.Document);
@@ -414,7 +414,7 @@ public static TheoryData<string, string, string, PluginAuthConfiguration, Action
             },
             // oauth2
             {
-                "{securitySchemes: {oauth2_0: {type: oauth2, flows: {}}}}",
+                "{securitySchemes: {oauth2_0: {type: oauth2, flows: {}, x-ai-auth-reference-id: auth1234}}}",
                 string.Empty, "security: [oauth2_0: []]", null, resultingManifest =>
                 {
                     Assert.NotNull(resultingManifest.Document);
@@ -423,7 +423,7 @@ public static TheoryData<string, string, string, PluginAuthConfiguration, Action
                     var auth0 = resultingManifest.Document.Runtimes[0].Auth;
                     Assert.IsType<OAuthPluginVault>(auth0);
                     Assert.Equal(AuthType.OAuthPluginVault, auth0?.Type);
-                    Assert.Equal("{oauth2_0_CONFIGURATION_ID}", ((OAuthPluginVault)auth0!).ReferenceId);
+                    Assert.Equal("auth1234", ((OAuthPluginVault)auth0!).ReferenceId);
                 }
             },
             // should be anonymous
@@ -560,8 +560,8 @@ public async Task GeneratesManifestWithMultipleSecuritySchemesAsync()
                               components:
                                 {
                                   securitySchemes: {
-                                    apiKey0: { type: apiKey, name: x-api-key0, in: header },
-                                    apiKey1: { type: apiKey, name: x-api-key1, in: header },
+                                    apiKey0: { type: apiKey, name: x-api-key0, in: header, x-ai-auth-reference-id: auth1234 },
+                                    apiKey1: { type: apiKey, name: x-api-key1, in: header, x-ai-auth-reference-id: auth5678 },
                                   },
                                 }
                               """;
@@ -604,11 +604,11 @@ public async Task GeneratesManifestWithMultipleSecuritySchemesAsync()
         var auth0 = resultingManifest.Document.Runtimes[0].Auth;
         Assert.IsType<ApiKeyPluginVault>(auth0);
         Assert.Equal(AuthType.ApiKeyPluginVault, auth0.Type);
-        Assert.Equal("{apiKey0_REGISTRATION_ID}", ((ApiKeyPluginVault)auth0!).ReferenceId);
+        Assert.Equal("auth1234", ((ApiKeyPluginVault)auth0!).ReferenceId);
         var auth1 = resultingManifest.Document.Runtimes[1].Auth;
         Assert.IsType<ApiKeyPluginVault>(auth1);
         Assert.Equal(AuthType.ApiKeyPluginVault, auth1.Type);
-        Assert.Equal("{apiKey1_REGISTRATION_ID}", ((ApiKeyPluginVault)auth1!).ReferenceId);
+        Assert.Equal("auth5678", ((ApiKeyPluginVault)auth1!).ReferenceId);
         // Cleanup
         try
         {