diff --git a/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs b/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs
index b92be76a52..4199e6847f 100644
--- a/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs
+++ b/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs
@@ -49,7 +49,7 @@ public static class Example22_OpenApiPlugin_AzureKeyVault
var plugin = await kernel.ImportPluginFunctionsAsync(
PluginResourceNames.AzureKeyVault,
stream!,
- new OpenApiFunctionExecutionParameters { AuthCallback = authenticationProvider.AuthenticateRequestAsync });
+ new OpenApiFunctionExecutionParameters { AuthenticateCallbackProvider = (manifest) => authenticationProvider.AuthenticateRequestAsync });
// Add arguments for required parameters, arguments for optional ones can be skipped.
var contextVariables = new ContextVariables();
@@ -80,7 +80,7 @@ public static class Example22_OpenApiPlugin_AzureKeyVault
stream!,
new OpenApiFunctionExecutionParameters
{
- AuthCallback = authenticationProvider.AuthenticateRequestAsync,
+ AuthenticateCallbackProvider = _ => authenticationProvider.AuthenticateRequestAsync,
EnableDynamicPayload = true
});
diff --git a/dotnet/samples/KernelSyntaxExamples/Example23_OpenApiPlugin_Github.cs b/dotnet/samples/KernelSyntaxExamples/Example23_OpenApiPlugin_Github.cs
index f7872ce320..d1128a8ccb 100644
--- a/dotnet/samples/KernelSyntaxExamples/Example23_OpenApiPlugin_Github.cs
+++ b/dotnet/samples/KernelSyntaxExamples/Example23_OpenApiPlugin_Github.cs
@@ -35,7 +35,7 @@ public static class Example23_OpenApiPlugin_GitHub
var plugin = await kernel.ImportPluginFunctionsAsync(
"GitHubPlugin",
"../../../../../../samples/dotnet/OpenApiPluginsExample/GitHubPlugin/openapi.json",
- new OpenApiFunctionExecutionParameters { AuthCallback = authenticationProvider.AuthenticateRequestAsync });
+ new OpenApiFunctionExecutionParameters { AuthenticateCallbackProvider = _ => authenticationProvider.AuthenticateRequestAsync });
// Add arguments for required parameters, arguments for optional ones can be skipped.
var contextVariables = new ContextVariables();
@@ -69,7 +69,7 @@ public static class Example23_OpenApiPlugin_GitHub
var plugin = await kernel.ImportPluginFunctionsAsync(
"GitHubPlugin",
"../../../../../../samples/dotnet/OpenApiPluginsExample/GitHubPlugin/openapi.json",
- new OpenApiFunctionExecutionParameters { AuthCallback = authenticationProvider.AuthenticateRequestAsync });
+ new OpenApiFunctionExecutionParameters { AuthenticateCallbackProvider = _ => authenticationProvider.AuthenticateRequestAsync });
// Add arguments for required parameters, arguments for optional ones can be skipped.
var contextVariables = new ContextVariables();
diff --git a/dotnet/samples/KernelSyntaxExamples/Example24_OpenApiPlugin_Jira.cs b/dotnet/samples/KernelSyntaxExamples/Example24_OpenApiPlugin_Jira.cs
index 41c4188e21..73ae6a9335 100644
--- a/dotnet/samples/KernelSyntaxExamples/Example24_OpenApiPlugin_Jira.cs
+++ b/dotnet/samples/KernelSyntaxExamples/Example24_OpenApiPlugin_Jira.cs
@@ -43,12 +43,12 @@ public static class Example24_OpenApiPlugin_Jira
if (useLocalFile)
{
var apiPluginFile = "./../../../Plugins/JiraPlugin/openapi.json";
- jiraFunctions = await kernel.ImportPluginFunctionsAsync("jiraPlugin", apiPluginFile, new OpenApiFunctionExecutionParameters(authCallback: tokenProvider.AuthenticateRequestAsync));
+ jiraFunctions = await kernel.ImportPluginFunctionsAsync("jiraPlugin", apiPluginFile, new OpenApiFunctionExecutionParameters(authCallbackProvider: (_) => tokenProvider.AuthenticateRequestAsync));
}
else
{
var apiPluginRawFileURL = new Uri("https://raw.githubusercontent.com/microsoft/PowerPlatformConnectors/dev/certified-connectors/JIRA/apiDefinition.swagger.json");
- jiraFunctions = await kernel.ImportPluginFunctionsAsync("jiraPlugin", apiPluginRawFileURL, new OpenApiFunctionExecutionParameters(httpClient, tokenProvider.AuthenticateRequestAsync));
+ jiraFunctions = await kernel.ImportPluginFunctionsAsync("jiraPlugin", apiPluginRawFileURL, new OpenApiFunctionExecutionParameters(authCallbackProvider: (_) => tokenProvider.AuthenticateRequestAsync));
}
// GetIssue Function
diff --git a/dotnet/src/Functions/Functions.OpenAPI/Authentication/AuthenticateCallbackProvider.cs b/dotnet/src/Functions/Functions.OpenAPI/Authentication/AuthenticateCallbackProvider.cs
new file mode 100644
index 0000000000..64b1abd2ff
--- /dev/null
+++ b/dotnet/src/Functions/Functions.OpenAPI/Authentication/AuthenticateCallbackProvider.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.SemanticKernel.Functions.OpenAPI.Authentication;
+
+///
+/// Represents a delegate that provides authentication callback based on plugin manifest auth config.
+///
+/// A representing the asynchronous operation.
+public delegate AuthenticateRequestAsyncCallback AuthenticateCallbackProvider(OpenAIAuthenticationManifest? manifest = null);
diff --git a/dotnet/src/Functions/Functions.OpenAPI/Authentication/OpenAIAuthenticationManifest.cs b/dotnet/src/Functions/Functions.OpenAPI/Authentication/OpenAIAuthenticationManifest.cs
new file mode 100644
index 0000000000..815f975862
--- /dev/null
+++ b/dotnet/src/Functions/Functions.OpenAPI/Authentication/OpenAIAuthenticationManifest.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.SemanticKernel.Functions.OpenAPI.Authentication;
+
+///
+/// Represents the authentication configuration for an OpenAI plugin.
+///
+public record OpenAIAuthenticationManifest
+{
+ ///
+ /// The type of authentication.
+ ///
+ public OpenAIAuthenticationType Type { get; set; } = OpenAIAuthenticationType.None;
+
+ ///
+ /// The type of authorization.
+ ///
+ public OpenAIAuthorizationType? AuthorizationType { get; set; }
+
+ ///
+ /// The client URL.
+ ///
+ public Uri? ClientUrl { get; set; }
+
+ ///
+ /// The authorization URL.
+ ///
+ public Uri? AuthorizationUrl { get; set; }
+
+ ///
+ /// The authorization content type.
+ ///
+ public string? AuthorizationContentType { get; set; }
+
+ ///
+ /// The authorization scope.
+ ///
+ public string? Scope { get; set; }
+
+ ///
+ /// The verification tokens.
+ ///
+ public Dictionary? VerificationTokens { get; set; }
+}
+
+///
+/// Represents the type of authentication for an OpenAI plugin.
+///
+public enum OpenAIAuthenticationType
+{
+ ///
+ /// No authentication.
+ ///
+ [EnumMember(Value = "none")]
+ None,
+
+ ///
+ /// User HTTP authentication.
+ ///
+ [EnumMember(Value = "user_http")]
+ UserHttp,
+
+ ///
+ /// Service HTTP authentication.
+ ///
+ [EnumMember(Value = "service_http")]
+ ServiceHttp,
+
+ ///
+ /// OAuth authentication.
+ ///
+ [EnumMember(Value = "oauth")]
+ OAuth
+}
+
+///
+/// Represents the type of authorization for an OpenAI plugin.
+///
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum OpenAIAuthorizationType
+{
+ ///
+ /// Basic authorization.
+ ///
+ [EnumMember(Value = "basic")]
+ Basic,
+
+ ///
+ /// Bearer authorization.
+ ///
+ [EnumMember(Value = "bearer")]
+ Bearer
+}
diff --git a/dotnet/src/Functions/Functions.OpenAPI/Authentication/OpenAIAuthenticationProvider.cs b/dotnet/src/Functions/Functions.OpenAPI/Authentication/OpenAIAuthenticationProvider.cs
new file mode 100644
index 0000000000..897a047362
--- /dev/null
+++ b/dotnet/src/Functions/Functions.OpenAPI/Authentication/OpenAIAuthenticationProvider.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+
+namespace Microsoft.SemanticKernel.Functions.OpenAPI.Authentication;
+
+///
+/// An example of OpenAI auth provider.
+///
+public class OpenAIAuthenticationProvider
+{
+ private readonly OpenAIAuthenticationManifest _manifest;
+
+ ///
+ /// Creates an instance of the class.
+ ///
+ /// OpenAI auth manifest
+ public OpenAIAuthenticationProvider(OpenAIAuthenticationManifest manifest)
+ {
+ this._manifest = manifest;
+ }
+
+ ///
+ /// Applies the token to the provided HTTP request message.
+ ///
+ /// The HTTP request message.
+ public async Task AuthenticateRequestAsync(HttpRequestMessage request)
+ {
+ var token = "fake-token"; //Implement all the required logic to parse the manifest and issue an auth token according to the manifest.
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
+ }
+}
diff --git a/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelAIPluginExtensions.cs b/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelAIPluginExtensions.cs
index ff0ba2a22f..8073cd6546 100644
--- a/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelAIPluginExtensions.cs
+++ b/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelAIPluginExtensions.cs
@@ -15,9 +15,13 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.Functions.OpenAPI.Authentication;
using Microsoft.SemanticKernel.Functions.OpenAPI.Model;
using Microsoft.SemanticKernel.Functions.OpenAPI.OpenApi;
using Microsoft.SemanticKernel.Orchestration;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using Newtonsoft.Json;
namespace Microsoft.SemanticKernel.Functions.OpenAPI.Extensions;
@@ -119,8 +123,9 @@ public static class KernelAIPluginExtensions
var pluginContents = await LoadDocumentFromUriAsync(
kernel,
uri,
- executionParameters,
httpClient,
+ executionParameters?.UserAgent,
+ executionParameters?.AuthenticateCallbackProvider?.Invoke(null),
cancellationToken).ConfigureAwait(false);
return await CompleteImportAsync(
@@ -178,15 +183,27 @@ public static class KernelAIPluginExtensions
Uri? documentUri = null,
CancellationToken cancellationToken = default)
{
+ var document = pluginContents;
+
+ AuthenticateRequestAsyncCallback? authCallback = null;
+
if (TryParseAIPluginForUrl(pluginContents, out var openApiUrl))
{
- return await kernel
- .ImportPluginFunctionsAsync(
- pluginName,
- new Uri(openApiUrl),
- executionParameters,
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
+ if (TryParseAIPluginForAuth(document, out var openApiManifestAuth) && openApiManifestAuth!.Type != OpenAIAuthenticationType.None)
+ {
+ if (executionParameters?.AuthenticateCallbackProvider != null)
+ {
+ authCallback = executionParameters.AuthenticateCallbackProvider(openApiManifestAuth);
+ }
+ }
+
+ document = await LoadDocumentFromUriAsync(
+ kernel,
+ new Uri(openApiUrl),
+ httpClient,
+ executionParameters?.UserAgent,
+ authCallback,
+ cancellationToken).ConfigureAwait(false);
}
return await LoadPluginAsync(
@@ -194,7 +211,8 @@ public static class KernelAIPluginExtensions
pluginName,
executionParameters,
httpClient,
- pluginContents,
+ document,
+ authCallback,
documentUri,
cancellationToken).ConfigureAwait(false);
}
@@ -205,6 +223,7 @@ public static class KernelAIPluginExtensions
OpenApiFunctionExecutionParameters? executionParameters,
HttpClient httpClient,
string pluginJson,
+ AuthenticateRequestAsyncCallback? authCallback,
Uri? documentUri = null,
CancellationToken cancellationToken = default)
{
@@ -216,7 +235,7 @@ public static class KernelAIPluginExtensions
var runner = new RestApiOperationRunner(
httpClient,
- executionParameters?.AuthCallback,
+ authCallback,
executionParameters?.UserAgent,
executionParameters?.EnableDynamicPayload ?? false,
executionParameters?.EnablePayloadNamespacing ?? false);
@@ -247,15 +266,20 @@ public static class KernelAIPluginExtensions
private static async Task LoadDocumentFromUriAsync(
IKernel kernel,
Uri uri,
- OpenApiFunctionExecutionParameters? executionParameters,
HttpClient httpClient,
+ string? userAgent,
+ AuthenticateRequestAsyncCallback? authCallback,
CancellationToken cancellationToken)
{
- using var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri.ToString());
+ using var request = new HttpRequestMessage(HttpMethod.Get, uri.ToString());
+ request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse(userAgent ?? Telemetry.HttpUserAgent));
- requestMessage.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse(executionParameters?.UserAgent ?? Telemetry.HttpUserAgent));
+ if (authCallback != null)
+ {
+ await authCallback(request).ConfigureAwait(false);
+ }
- using var response = await httpClient.SendWithSuccessCheckAsync(requestMessage, cancellationToken).ConfigureAwait(false);
+ using var response = await httpClient.SendWithSuccessCheckAsync(request, cancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsStringWithExceptionMappingAsync().ConfigureAwait(false);
}
@@ -322,6 +346,31 @@ public static class KernelAIPluginExtensions
}
}
+ private static bool TryParseAIPluginForAuth(string gptPluginJson, out OpenAIAuthenticationManifest? openApiManifestAuth)
+ {
+ try
+ {
+ var serializerSettings = new JsonSerializerSettings
+ {
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new SnakeCaseNamingStrategy()
+ }
+ };
+
+ JObject? gptPlugin = JsonConvert.DeserializeObject(gptPluginJson, serializerSettings);
+ openApiManifestAuth = gptPlugin?["auth"]?.ToObject(JsonSerializer.Create(serializerSettings));
+
+ return openApiManifestAuth != null;
+ }
+ catch (System.Text.Json.JsonException)
+ {
+ openApiManifestAuth = null;
+
+ return false;
+ }
+ }
+
///
/// Registers SKFunction for a REST API operation.
///
diff --git a/dotnet/src/Functions/Functions.OpenAPI/Extensions/OpenApiFunctionExecutionParameters.cs b/dotnet/src/Functions/Functions.OpenAPI/Extensions/OpenApiFunctionExecutionParameters.cs
index 5d7a045b19..f611e8fa4d 100644
--- a/dotnet/src/Functions/Functions.OpenAPI/Extensions/OpenApiFunctionExecutionParameters.cs
+++ b/dotnet/src/Functions/Functions.OpenAPI/Extensions/OpenApiFunctionExecutionParameters.cs
@@ -20,8 +20,14 @@ public class OpenApiFunctionExecutionParameters
///
/// Callback for adding authentication data to HTTP requests.
///
+ [Obsolete("This property will be removed in one of the next releases. Please use the AuthenticateCallbackProvider property instead.")]
public AuthenticateRequestAsyncCallback? AuthCallback { get; set; }
+ ///
+ /// Provider to get authentication callback.
+ ///
+ public AuthenticateCallbackProvider? AuthenticateCallbackProvider { get; set; }
+
///
/// Override for REST API operation server url.
///
@@ -58,8 +64,8 @@ public class OpenApiFunctionExecutionParameters
/// Initializes a new instance of the class.
///
/// The HttpClient to use for sending HTTP requests.
- /// The callback for adding authentication data to HTTP requests.
/// The override for the REST API operation server URL.
+ /// The provider to return callback for adding authentication data to HTTP requests.
/// Optional user agent header value.
/// A flag indicating whether to ignore non-compliant errors or not
/// If set to true, the operation execution will not throw exceptions for non-compliant documents.
@@ -70,15 +76,15 @@ public class OpenApiFunctionExecutionParameters
/// Namespaces prevent naming conflicts by adding the parent parameter name as a prefix, separated by dots.
public OpenApiFunctionExecutionParameters(
HttpClient? httpClient = null,
- AuthenticateRequestAsyncCallback? authCallback = null,
Uri? serverUrlOverride = null,
+ AuthenticateCallbackProvider? authCallbackProvider = null,
string userAgent = Telemetry.HttpUserAgent,
bool ignoreNonCompliantErrors = false,
bool enableDynamicOperationPayload = false,
bool enablePayloadNamespacing = false)
{
this.HttpClient = httpClient;
- this.AuthCallback = authCallback;
+ this.AuthenticateCallbackProvider = authCallbackProvider;
this.ServerUrlOverride = serverUrlOverride;
this.UserAgent = userAgent;
this.IgnoreNonCompliantErrors = ignoreNonCompliantErrors;
diff --git a/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj b/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj
index cc3d8351e0..4a210a75c4 100644
--- a/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj
+++ b/dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj
@@ -16,6 +16,7 @@
+
@@ -23,6 +24,7 @@
+
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelAIPluginExtensionsTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelAIPluginExtensionsTests.cs
index d8d34f1f1f..b481b3bfed 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelAIPluginExtensionsTests.cs
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelAIPluginExtensionsTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -9,10 +10,13 @@ using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Diagnostics;
+using Microsoft.SemanticKernel.Functions.OpenAPI.Authentication;
using Microsoft.SemanticKernel.Functions.OpenAPI.Extensions;
using Microsoft.SemanticKernel.Functions.OpenAPI.Model;
using Microsoft.SemanticKernel.Functions.OpenAPI.OpenApi;
using Microsoft.SemanticKernel.Orchestration;
+using Moq;
using SemanticKernel.Functions.UnitTests.OpenAPI.TestPlugins;
using Xunit;
@@ -47,6 +51,60 @@ public sealed class KernelAIPluginExtensionsTests : IDisposable
this._sut = new OpenApiDocumentParser();
}
+ [Fact]
+ public async Task ItUsesAuthFromOpenAiPluginManifestWhenFetchingOpenApiSpecAsync()
+ {
+ //Arrange
+ using var messageHandlerStub = new HttpMessageHandlerStub((HttpRequestMessage message) =>
+ {
+ if (message!.RequestUri!.AbsoluteUri == "https://fake-random-test-host/openai_manifest.json")
+ {
+ var response = new HttpResponseMessage();
+ response.Content = new StreamContent(ResourcePluginsProvider.LoadFromResource("ai-plugin.json"));
+ return response;
+ }
+
+ if (message!.RequestUri!.AbsoluteUri == "https://fake-random-test-host/openapi.json")
+ {
+ var response = new HttpResponseMessage();
+ response.Content = new StreamContent(this._openApiDocument);
+ return response;
+ }
+
+ throw new SKException($"Unexpected url - {message!.RequestUri!.AbsoluteUri}");
+ });
+
+ using var httpClient = new HttpClient(messageHandlerStub, false);
+
+ var providerActualArguments = new List();
+
+ var authCallbackMock = new Mock();
+
+ var authCallbackProviderMock = new Mock();
+ authCallbackProviderMock
+ .Setup(p => p.Invoke(It.IsAny()))
+ .Callback(c => providerActualArguments.Add(c))
+ .Returns(authCallbackMock.Object);
+
+ //Act
+ var plugin = await this._kernel.ImportPluginFunctionsAsync(
+ "fakePlugin",
+ new Uri("https://fake-random-test-host/openai_manifest.json"),
+ new OpenApiFunctionExecutionParameters { HttpClient = httpClient, AuthenticateCallbackProvider = authCallbackProviderMock.Object });
+
+ //Assert
+ var setSecretFunction = plugin["SetSecret"];
+ Assert.NotNull(setSecretFunction);
+
+ //Check provider is called two times: first time with null when loading the OpenAI manifest document and the second time with auth config when loading the OpenAPI document.
+ authCallbackProviderMock.Verify(target => target.Invoke(It.IsAny()), Times.Exactly(2));
+ Assert.Equal(2, providerActualArguments.Count);
+ Assert.Null(providerActualArguments[0]);
+ Assert.Equal("https://vault.azure.net/.default", providerActualArguments[1]?.Scope);
+
+ authCallbackMock.Verify(target => target.Invoke(It.IsAny()), Times.Exactly(2));
+ }
+
[Fact]
public async Task ItCanIncludeOpenApiOperationParameterTypesIntoFunctionParametersViewAsync()
{
@@ -203,8 +261,8 @@ public sealed class KernelAIPluginExtensionsTests : IDisposable
public async Task ItShouldConvertPluginComplexResponseToStringToSaveItInContextAsync()
{
//Arrange
- using var messageHandlerStub = new HttpMessageHandlerStub();
- messageHandlerStub.ResponseToReturn.Content = new StringContent("fake-content", Encoding.UTF8, MediaTypeNames.Application.Json);
+ using var content = new StringContent("fake-content", Encoding.UTF8, MediaTypeNames.Application.Json);
+ using var messageHandlerStub = new HttpMessageHandlerStub(content);
using var httpClient = new HttpClient(messageHandlerStub, false);
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/HttpMessageHandlerStub.cs b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/HttpMessageHandlerStub.cs
index 542aa33a7c..c7ebba55f1 100644
--- a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/HttpMessageHandlerStub.cs
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/HttpMessageHandlerStub.cs
@@ -23,24 +23,51 @@ internal sealed class HttpMessageHandlerStub : DelegatingHandler
public HttpMethod? Method { get; private set; }
- public HttpResponseMessage ResponseToReturn { get; set; }
+ public Func ResponseToReturnProvider { get; set; }
public HttpMessageHandlerStub()
{
- this.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
- this.ResponseToReturn.Content = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json);
+ this.ResponseToReturnProvider = (_) =>
+ {
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
+ response.Content = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json);
+ return response;
+ };
}
public HttpMessageHandlerStub(Stream responseToReturn)
{
- this.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
- this.ResponseToReturn.Content = new StreamContent(responseToReturn);
+ this.ResponseToReturnProvider = (_) =>
+ {
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
+ response.Content = new StreamContent(responseToReturn);
+ return response;
+ };
+ }
+
+ public HttpMessageHandlerStub(HttpContent content)
+ {
+ this.ResponseToReturnProvider = (_) =>
+ {
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
+ response.Content = content;
+ return response;
+ };
+ }
+
+ public HttpMessageHandlerStub(Func responseToReturn)
+ {
+ this.ResponseToReturnProvider = responseToReturn;
}
public void ResetResponse()
{
- this.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
- this.ResponseToReturn.Content = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json);
+ this.ResponseToReturnProvider = (_) =>
+ {
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
+ response.Content = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json);
+ return response;
+ };
}
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
@@ -51,6 +78,6 @@ internal sealed class HttpMessageHandlerStub : DelegatingHandler
this.RequestContent = request.Content == null ? null : await request.Content.ReadAsByteArrayAsync(cancellationToken);
this.ContentHeaders = request.Content?.Headers;
- return await Task.FromResult(this.ResponseToReturn);
+ return await Task.FromResult(this.ResponseToReturnProvider(request));
}
}
diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/TestPlugins/ai-plugin.json b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/TestPlugins/ai-plugin.json
new file mode 100644
index 0000000000..5b58f7847d
--- /dev/null
+++ b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/TestPlugins/ai-plugin.json
@@ -0,0 +1,24 @@
+{
+ "schema_version": "v1",
+ "name_for_human": "AzureKeyVault",
+ "name_for_model": "AzureKeyVault",
+ "description_for_human": "Query and interact with Azure Key Vault",
+ "description_for_model": "Query and interact with Azure Key Vault",
+ "auth": {
+ "type": "oauth",
+ "client_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
+ "scope": "https://vault.azure.net/.default",
+ "authorization_url": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
+ "authorization_content_type": "application/x-www-form-urlencoded",
+ "verification_tokens": {
+ "openai": "00000000000000000000000000000000"
+ }
+ },
+ "api": {
+ "type": "openapi",
+ "url": "https://fake-random-test-host/openapi.json"
+ },
+ "logo_url": "https://contoso.com/logo.png",
+ "contact_email": "contact@contoso.com",
+ "legal_info_url": "https://privacy.microsoft.com/en-US/privacystatement"
+}
\ No newline at end of file