// Copyright (c) Microsoft. All rights reserved. using System.Net; using System.Text; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Plugins.OpenApi; namespace Plugins; /// /// Sample shows how to register the to transform existing or create new . /// public sealed class OpenApiPlugin_RestApiOperationResponseFactory(ITestOutputHelper output) : BaseTest(output) { private readonly HttpClient _httpClient = new(new StubHttpHandler(InterceptRequestAndCustomizeResponseAsync)); [Fact] public async Task IncludeResponseHeadersToOperationResponseAsync() { Kernel kernel = new(); // Register the operation response factory and the custom HTTP client OpenApiFunctionExecutionParameters executionParameters = new() { RestApiOperationResponseFactory = IncludeHeadersIntoRestApiOperationResponseAsync, HttpClient = this._httpClient }; // Create OpenAPI plugin KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("RepairService", "Resources/Plugins/RepairServicePlugin/repair-service.json", executionParameters); // Create arguments for a new repair KernelArguments arguments = new() { ["title"] = "The Case of the Broken Gizmo", ["description"] = "It's broken. Send help!", ["assignedTo"] = "Tech Magician" }; // Create the repair FunctionResult createResult = await plugin["createRepair"].InvokeAsync(kernel, arguments); // Get operation response that was modified RestApiOperationResponse response = createResult.GetValue()!; // Display the 'repair-id' header value Console.WriteLine(response.Headers!["repair-id"].First()); } /// /// A custom factory to transform the operation response. /// /// The context for the . /// The cancellation token. /// The transformed operation response. private static async Task IncludeHeadersIntoRestApiOperationResponseAsync(RestApiOperationResponseFactoryContext context, CancellationToken cancellationToken) { // Create the response using the internal factory RestApiOperationResponse response = await context.InternalFactory(context, cancellationToken); // Obtain the 'repair-id' header value from the HTTP response and include it in the operation response only for the 'createRepair' operation if (context.Operation.Id == "createRepair" && context.Response.Headers.TryGetValues("repair-id", out IEnumerable? values)) { response.Headers ??= new Dictionary>(); response.Headers["repair-id"] = values; } // Include the request options in the operation response if (context.Request.Options is not null) { response.Data ??= new Dictionary(); response.Data["http.request.options"] = context.Request.Options; } // Return the modified response that will be returned to the caller return response; } /// /// A custom HTTP handler to intercept HTTP requests and return custom responses. /// /// The original HTTP request. /// The custom HTTP response. private static async Task InterceptRequestAndCustomizeResponseAsync(HttpRequestMessage request) { // Return a mock response that includes the 'repair-id' header for the 'createRepair' operation if (request.RequestUri!.AbsolutePath == "/repairs" && request.Method == HttpMethod.Post) { return new HttpResponseMessage(HttpStatusCode.Created) { Content = new StringContent("Success", Encoding.UTF8, "application/json"), Headers = { { "repair-id", "repair-12345" } } }; } return new HttpResponseMessage(HttpStatusCode.NoContent); } private sealed class StubHttpHandler(Func> requestHandler) : DelegatingHandler() { private readonly Func> _requestHandler = requestHandler; protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return await this._requestHandler(request); } } protected override void Dispose(bool disposing) { base.Dispose(disposing); this._httpClient.Dispose(); } }