From 5be997456bbb125159c9995919b227ea5415777c Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 15:01:23 +0100
Subject: [PATCH 01/11] Add utility for ResponseTool
---
...icrosoftExtensionsAIResponsesExtensions.cs | 7 ++
.../OpenAIResponsesChatClient.cs | 99 +++++++++++++++++++
2 files changed, 106 insertions(+)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
index 0d119a742e2..7ce897f7543 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
@@ -21,6 +21,13 @@ public static class MicrosoftExtensionsAIResponsesExtensions
public static FunctionTool AsOpenAIResponseTool(this AIFunctionDeclaration function) =>
OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(function));
+ /// Creates an OpenAI from an .
+ /// The tool to convert.
+ /// An OpenAI representing or if there is no mapping.
+ /// is .
+ public static ResponseTool? AsOpenAIResponseTool(this AITool tool)
+ => OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(tool));
+
///
/// Creates an OpenAI from a .
///
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index 039d04c1f8e..02b780b99e3 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -431,6 +431,105 @@ void IDisposable.Dispose()
// Nothing to dispose. Implementation required for the IChatClient interface.
}
+ internal static ResponseTool? ToResponseTool(AITool tool, ChatOptions? options = null)
+ {
+ switch (tool)
+ {
+ case ResponseToolAITool rtat:
+ return rtat.Tool;
+
+ case AIFunctionDeclaration aiFunction:
+ bool? strict =
+ OpenAIClientExtensions.HasStrict(aiFunction.AdditionalProperties) ??
+ OpenAIClientExtensions.HasStrict(options?.AdditionalProperties);
+
+ return ResponseTool.CreateFunctionTool(
+ aiFunction.Name,
+ OpenAIClientExtensions.ToOpenAIFunctionParameters(aiFunction, strict),
+ strict,
+ aiFunction.Description);
+
+ case HostedWebSearchTool webSearchTool:
+ WebSearchToolLocation? location = null;
+ if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
+ {
+ location = objLocation as WebSearchToolLocation;
+ }
+
+ WebSearchToolContextSize? size = null;
+ if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
+ objSize is WebSearchToolContextSize)
+ {
+ size = (WebSearchToolContextSize)objSize;
+ }
+
+ return ResponseTool.CreateWebSearchTool(location, size);
+
+ case HostedFileSearchTool fileSearchTool:
+ return ResponseTool.CreateFileSearchTool(
+ fileSearchTool.Inputs?.OfType().Select(c => c.VectorStoreId) ?? [],
+ fileSearchTool.MaximumResultCount);
+
+ case HostedCodeInterpreterTool codeTool:
+ return ResponseTool.CreateCodeInterpreterTool(
+ new CodeInterpreterToolContainer(codeTool.Inputs?.OfType().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
+ CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
+ new()));
+
+ case HostedMcpServerTool mcpTool:
+ McpTool responsesMcpTool = Uri.TryCreate(mcpTool.ServerAddress, UriKind.Absolute, out Uri? url) ?
+ ResponseTool.CreateMcpTool(
+ mcpTool.ServerName,
+ url,
+ mcpTool.AuthorizationToken,
+ mcpTool.ServerDescription) :
+ ResponseTool.CreateMcpTool(
+ mcpTool.ServerName,
+ new McpToolConnectorId(mcpTool.ServerAddress),
+ mcpTool.AuthorizationToken,
+ mcpTool.ServerDescription);
+
+ if (mcpTool.AllowedTools is not null)
+ {
+ responsesMcpTool.AllowedTools = new();
+ AddAllMcpFilters(mcpTool.AllowedTools, responsesMcpTool.AllowedTools);
+ }
+
+ switch (mcpTool.ApprovalMode)
+ {
+ case HostedMcpServerToolAlwaysRequireApprovalMode:
+ responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.AlwaysRequireApproval);
+ break;
+
+ case HostedMcpServerToolNeverRequireApprovalMode:
+ responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval);
+ break;
+
+ case HostedMcpServerToolRequireSpecificApprovalMode specificMode:
+ responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(new CustomMcpToolCallApprovalPolicy());
+
+ if (specificMode.AlwaysRequireApprovalToolNames is { Count: > 0 } alwaysRequireToolNames)
+ {
+ responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval = new();
+ AddAllMcpFilters(alwaysRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval);
+ }
+
+ if (specificMode.NeverRequireApprovalToolNames is { Count: > 0 } neverRequireToolNames)
+ {
+ responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval = new();
+ AddAllMcpFilters(neverRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval);
+ }
+
+ break;
+ }
+
+ return responsesMcpTool;
+
+ default:
+ return null;
+ }
+ }
+
internal static FunctionTool ToResponseTool(AIFunctionDeclaration aiFunction, ChatOptions? options = null)
{
bool? strict =
From a23c230308a8c8ca40c1aeb0767ea0ccef62f9dc Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 15:26:15 +0100
Subject: [PATCH 02/11] Adding UT for Utility
---
.../OpenAIConversionTests.cs | 168 ++++++++++++++++++
1 file changed, 168 insertions(+)
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
index 844fb5618ed..2ffc775a11a 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
@@ -112,6 +112,168 @@ public void AsOpenAIResponseTool_ProducesValidInstance()
Assert.NotNull(tool);
}
+ [Fact]
+ public void AsOpenAIResponseTool_WithAIFunctionDeclaration_ProducesValidFunctionTool()
+ {
+ var tool = _testFunction.AsOpenAIResponseTool();
+
+ Assert.NotNull(tool);
+ Assert.IsType(tool);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedWebSearchTool_ProducesValidWebSearchTool()
+ {
+ var webSearchTool = new HostedWebSearchTool();
+
+ var result = webSearchTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedFileSearchTool_ProducesValidFileSearchTool()
+ {
+ var fileSearchTool = new HostedFileSearchTool();
+
+ var result = fileSearchTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedFileSearchToolWithVectorStores_ProducesValidFileSearchTool()
+ {
+ var vectorStoreContent = new HostedVectorStoreContent("vector-store-123");
+ var fileSearchTool = new HostedFileSearchTool
+ {
+ Inputs = [vectorStoreContent]
+ };
+
+ var result = fileSearchTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.Single(tool.VectorStoreIds);
+ Assert.Equal(vectorStoreContent.VectorStoreId, tool.VectorStoreIds[0]);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedFileSearchToolWithMaxResults_ProducesValidFileSearchTool()
+ {
+ var fileSearchTool = new HostedFileSearchTool
+ {
+ MaximumResultCount = 10
+ };
+
+ var result = fileSearchTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.Equal(10, tool.MaxResultCount);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedCodeInterpreterTool_ProducesValidCodeInterpreterTool()
+ {
+ var codeTool = new HostedCodeInterpreterTool();
+
+ var result = codeTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedCodeInterpreterToolWithFiles_ProducesValidCodeInterpreterTool()
+ {
+ var fileContent = new HostedFileContent("file-123");
+ var codeTool = new HostedCodeInterpreterTool
+ {
+ Inputs = [fileContent]
+ };
+
+ var result = codeTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ var autoContainerConfig = Assert.IsType(tool.Container.ContainerConfiguration);
+ Assert.Single(autoContainerConfig.FileIds);
+ Assert.Equal(fileContent.FileId, autoContainerConfig.FileIds[0]);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerTool_ProducesValidMcpTool()
+ {
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000");
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.Equal(new Uri("http://localhost:8000"), tool.ServerUri);
+ Assert.Equal("test-server", tool.ServerLabel);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithDescription_ProducesValidMcpTool()
+ {
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000")
+ {
+ ServerDescription = "A test MCP server"
+ };
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.Equal("A test MCP server", tool.ServerDescription);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithAuthToken_ProducesValidMcpTool()
+ {
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000")
+ {
+ AuthorizationToken = "test-token"
+ };
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.Equal("test-token", tool.AuthorizationToken);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithUri_ProducesValidMcpTool()
+ {
+ var mcpTool = new HostedMcpServerTool("test-server", new Uri("http://localhost:8000"));
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithUnknownToolType_ReturnsNull()
+ {
+ var unknownTool = new UnknownAITool();
+
+ var result = unknownTool.AsOpenAIResponseTool();
+
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithNullTool_ThrowsArgumentNullException()
+ {
+ Assert.Throws("tool", () => ((AITool)null!).AsOpenAIResponseTool());
+ }
+
[Fact]
public void AsOpenAIConversationFunctionTool_ProducesValidInstance()
{
@@ -1256,4 +1418,10 @@ private static async IAsyncEnumerable CreateAsyncEnumerable(IEnumerable
}
private static string RemoveWhitespace(string input) => Regex.Replace(input, @"\s+", "");
+
+ /// Helper class for testing unknown tool types.
+ private sealed class UnknownAITool : AITool
+ {
+ public override string Name => "unknown_tool";
+ }
}
From df6ac55b68c89b151faeb3b8766df1112e7ab30d Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 15:33:33 +0100
Subject: [PATCH 03/11] Update internals to utility
---
.../OpenAIResponsesChatClient.cs | 92 +------------------
1 file changed, 3 insertions(+), 89 deletions(-)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index 02b780b99e3..6f46af072d0 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -591,96 +591,10 @@ private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? opt
{
foreach (AITool tool in tools)
{
- switch (tool)
+ var responseTool = ToResponseTool(tool, options);
+ if (responseTool is not null)
{
- case ResponseToolAITool rtat:
- result.Tools.Add(rtat.Tool);
- break;
-
- case AIFunctionDeclaration aiFunction:
- result.Tools.Add(ToResponseTool(aiFunction, options));
- break;
-
- case HostedWebSearchTool webSearchTool:
- WebSearchToolLocation? location = null;
- if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
- {
- location = objLocation as WebSearchToolLocation;
- }
-
- WebSearchToolContextSize? size = null;
- if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
- objSize is WebSearchToolContextSize)
- {
- size = (WebSearchToolContextSize)objSize;
- }
-
- result.Tools.Add(ResponseTool.CreateWebSearchTool(location, size));
- break;
-
- case HostedFileSearchTool fileSearchTool:
- result.Tools.Add(ResponseTool.CreateFileSearchTool(
- fileSearchTool.Inputs?.OfType().Select(c => c.VectorStoreId) ?? [],
- fileSearchTool.MaximumResultCount));
- break;
-
- case HostedCodeInterpreterTool codeTool:
- result.Tools.Add(
- ResponseTool.CreateCodeInterpreterTool(
- new CodeInterpreterToolContainer(codeTool.Inputs?.OfType().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
- CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
- new())));
- break;
-
- case HostedMcpServerTool mcpTool:
- McpTool responsesMcpTool = Uri.TryCreate(mcpTool.ServerAddress, UriKind.Absolute, out Uri? url) ?
- ResponseTool.CreateMcpTool(
- mcpTool.ServerName,
- url,
- mcpTool.AuthorizationToken,
- mcpTool.ServerDescription) :
- ResponseTool.CreateMcpTool(
- mcpTool.ServerName,
- new McpToolConnectorId(mcpTool.ServerAddress),
- mcpTool.AuthorizationToken,
- mcpTool.ServerDescription);
-
- if (mcpTool.AllowedTools is not null)
- {
- responsesMcpTool.AllowedTools = new();
- AddAllMcpFilters(mcpTool.AllowedTools, responsesMcpTool.AllowedTools);
- }
-
- switch (mcpTool.ApprovalMode)
- {
- case HostedMcpServerToolAlwaysRequireApprovalMode:
- responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.AlwaysRequireApproval);
- break;
-
- case HostedMcpServerToolNeverRequireApprovalMode:
- responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval);
- break;
-
- case HostedMcpServerToolRequireSpecificApprovalMode specificMode:
- responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(new CustomMcpToolCallApprovalPolicy());
-
- if (specificMode.AlwaysRequireApprovalToolNames is { Count: > 0 } alwaysRequireToolNames)
- {
- responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval = new();
- AddAllMcpFilters(alwaysRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval);
- }
-
- if (specificMode.NeverRequireApprovalToolNames is { Count: > 0 } neverRequireToolNames)
- {
- responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval = new();
- AddAllMcpFilters(neverRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval);
- }
-
- break;
- }
-
- result.Tools.Add(responsesMcpTool);
- break;
+ result.Tools.Add(responseTool);
}
}
From 4eeef7cb85022a7e99ff57c311983e91bcc5a608 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 15:44:38 +0100
Subject: [PATCH 04/11] Update
src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
Co-authored-by: Stephen Toub
---
.../OpenAIResponsesChatClient.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index 6f46af072d0..cb47a0a2b88 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -591,8 +591,7 @@ private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? opt
{
foreach (AITool tool in tools)
{
- var responseTool = ToResponseTool(tool, options);
- if (responseTool is not null)
+ if (ToResponseTool(tool, options) is { } responseTool)
{
result.Tools.Add(responseTool);
}
From 5e0d9d1e2fba280da4a0b930755ee9d286a68620 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 16:00:06 +0100
Subject: [PATCH 05/11] Update
src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
Co-authored-by: Stephen Toub
---
.../MicrosoftExtensionsAIResponsesExtensions.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
index 7ce897f7543..0562a34ca06 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
@@ -25,6 +25,10 @@ public static FunctionTool AsOpenAIResponseTool(this AIFunctionDeclaration funct
/// The tool to convert.
/// An OpenAI representing or if there is no mapping.
/// is .
+ ///
+ /// This method is only able to create s for types
+ /// it's aware of, namely all of those available from the Microsoft.Extensions.AI.Abstractions library.
+ ///
public static ResponseTool? AsOpenAIResponseTool(this AITool tool)
=> OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(tool));
From e71090ec3df340060a884f60808f903fbe319df1 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 16:00:38 +0100
Subject: [PATCH 06/11] Update
src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
Co-authored-by: Stephen Toub
---
.../OpenAIResponsesChatClient.cs | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index cb47a0a2b88..12dd8a12c42 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -439,15 +439,7 @@ void IDisposable.Dispose()
return rtat.Tool;
case AIFunctionDeclaration aiFunction:
- bool? strict =
- OpenAIClientExtensions.HasStrict(aiFunction.AdditionalProperties) ??
- OpenAIClientExtensions.HasStrict(options?.AdditionalProperties);
-
- return ResponseTool.CreateFunctionTool(
- aiFunction.Name,
- OpenAIClientExtensions.ToOpenAIFunctionParameters(aiFunction, strict),
- strict,
- aiFunction.Description);
+ return ToResponseTool(aiFunction, options);
case HostedWebSearchTool webSearchTool:
WebSearchToolLocation? location = null;
From 54bfcdb4cb6b3dd0b1c35effe9ac269ef45670ff Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 16:00:59 +0100
Subject: [PATCH 07/11] Update
src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
Co-authored-by: Stephen Toub
---
.../OpenAIResponsesChatClient.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index 12dd8a12c42..c1617dc0d08 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -464,9 +464,9 @@ void IDisposable.Dispose()
case HostedCodeInterpreterTool codeTool:
return ResponseTool.CreateCodeInterpreterTool(
- new CodeInterpreterToolContainer(codeTool.Inputs?.OfType().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
- CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
- new()));
+ new CodeInterpreterToolContainer(codeTool.Inputs?.OfType().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
+ CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
+ new()));
case HostedMcpServerTool mcpTool:
McpTool responsesMcpTool = Uri.TryCreate(mcpTool.ServerAddress, UriKind.Absolute, out Uri? url) ?
From 0f8976c19231e91bd547f5ba091164e032ef68e2 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 16:46:35 +0100
Subject: [PATCH 08/11] Update test to use the AITool extension directly
---
.../OpenAIConversionTests.cs | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
index 2ffc775a11a..27f943daaba 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
@@ -113,12 +113,14 @@ public void AsOpenAIResponseTool_ProducesValidInstance()
}
[Fact]
- public void AsOpenAIResponseTool_WithAIFunctionDeclaration_ProducesValidFunctionTool()
+ public void AsOpenAIResponseTool_WithAIFunctionTool_ProducesValidFunctionTool()
{
- var tool = _testFunction.AsOpenAIResponseTool();
+ var tool = MicrosoftExtensionsAIResponsesExtensions.AsOpenAIResponseTool(tool: _testFunction);
Assert.NotNull(tool);
- Assert.IsType(tool);
+ var functionTool = Assert.IsType(tool);
+ Assert.Equal("test_function", functionTool.FunctionName);
+ Assert.Equal("A test function for conversion", functionTool.FunctionDescription);
}
[Fact]
From 6434d1b4808fa31251f036a9c9a3803ce9d71433 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 17:17:44 +0100
Subject: [PATCH 09/11] Improve UT
---
.../OpenAIConversionTests.cs | 56 +++++++++++++++++--
1 file changed, 51 insertions(+), 5 deletions(-)
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
index 27f943daaba..608aceedbf9 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
@@ -131,18 +131,46 @@ public void AsOpenAIResponseTool_WithHostedWebSearchTool_ProducesValidWebSearchT
var result = webSearchTool.AsOpenAIResponseTool();
Assert.NotNull(result);
- Assert.IsType(result);
+ var tool = Assert.IsType(result);
+ Assert.NotNull(tool);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedWebSearchToolWithAdditionalProperties_ProducesValidWebSearchTool()
+ {
+ var location = WebSearchToolLocation.CreateApproximateLocation("US", "Region", "City", "UTC");
+ var webSearchTool = new HostedWebSearchToolWithProperties(new Dictionary
+ {
+ [nameof(WebSearchToolLocation)] = location,
+ [nameof(WebSearchToolContextSize)] = WebSearchToolContextSize.High
+ });
+
+ var result = webSearchTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+
+ var tool = Assert.IsType(result);
+ Assert.Equal(WebSearchToolContextSize.High, tool.SearchContextSize);
+ Assert.NotNull(tool);
+
+ var approximateLocation = Assert.IsType(tool.UserLocation);
+ Assert.Equal(location.Country, approximateLocation.Country);
+ Assert.Equal(location.Region, approximateLocation.Region);
+ Assert.Equal(location.City, approximateLocation.City);
+ Assert.Equal(location.Timezone, approximateLocation.Timezone);
}
[Fact]
public void AsOpenAIResponseTool_WithHostedFileSearchTool_ProducesValidFileSearchTool()
{
- var fileSearchTool = new HostedFileSearchTool();
+ var fileSearchTool = new HostedFileSearchTool { MaximumResultCount = 10 };
var result = fileSearchTool.AsOpenAIResponseTool();
Assert.NotNull(result);
- Assert.IsType(result);
+ var tool = Assert.IsType(result);
+ Assert.Empty(tool.VectorStoreIds);
+ Assert.Equal(fileSearchTool.MaximumResultCount, tool.MaxResultCount);
}
[Fact]
@@ -186,6 +214,8 @@ public void AsOpenAIResponseTool_WithHostedCodeInterpreterTool_ProducesValidCode
Assert.NotNull(result);
var tool = Assert.IsType(result);
+ Assert.NotNull(tool.Container);
+ Assert.NotNull(tool.Container.ContainerConfiguration);
}
[Fact]
@@ -252,12 +282,15 @@ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithAuthToken_ProducesVa
[Fact]
public void AsOpenAIResponseTool_WithHostedMcpServerToolWithUri_ProducesValidMcpTool()
{
- var mcpTool = new HostedMcpServerTool("test-server", new Uri("http://localhost:8000"));
+ var expectedUri = new Uri("http://localhost:8000");
+ var mcpTool = new HostedMcpServerTool("test-server", expectedUri);
var result = mcpTool.AsOpenAIResponseTool();
Assert.NotNull(result);
- Assert.IsType(result);
+ var tool = Assert.IsType(result);
+ Assert.Equal(expectedUri, tool.ServerUri);
+ Assert.Equal("test-server", tool.ServerLabel);
}
[Fact]
@@ -1426,4 +1459,17 @@ private sealed class UnknownAITool : AITool
{
public override string Name => "unknown_tool";
}
+
+ /// Helper class for testing WebSearchTool with additional properties.
+ private sealed class HostedWebSearchToolWithProperties : HostedWebSearchTool
+ {
+ private readonly Dictionary _additionalProperties;
+
+ public override IReadOnlyDictionary AdditionalProperties => _additionalProperties;
+
+ public HostedWebSearchToolWithProperties(Dictionary additionalProperties)
+ {
+ _additionalProperties = additionalProperties;
+ }
+ }
}
From 08949d29a9de52a45218aa7d83e1bd5c58a8f62c Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 17:26:31 +0100
Subject: [PATCH 10/11] Add MCP missing UT
---
.../OpenAIConversionTests.cs | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
index 608aceedbf9..7fe1ceb8b57 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
@@ -293,6 +293,86 @@ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithUri_ProducesValidMcp
Assert.Equal("test-server", tool.ServerLabel);
}
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithAllowedTools_ProducesValidMcpTool()
+ {
+ var allowedTools = new List { "tool1", "tool2", "tool3" };
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000")
+ {
+ AllowedTools = allowedTools
+ };
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.NotNull(tool.AllowedTools);
+ Assert.Equal(3, tool.AllowedTools.ToolNames.Count);
+ Assert.Contains("tool1", tool.AllowedTools.ToolNames);
+ Assert.Contains("tool2", tool.AllowedTools.ToolNames);
+ Assert.Contains("tool3", tool.AllowedTools.ToolNames);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithAlwaysRequireApprovalMode_ProducesValidMcpTool()
+ {
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000")
+ {
+ ApprovalMode = HostedMcpServerToolApprovalMode.AlwaysRequire
+ };
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.NotNull(tool.ToolCallApprovalPolicy);
+ Assert.NotNull(tool.ToolCallApprovalPolicy.GlobalPolicy);
+ Assert.Equal(GlobalMcpToolCallApprovalPolicy.AlwaysRequireApproval, tool.ToolCallApprovalPolicy.GlobalPolicy);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithNeverRequireApprovalMode_ProducesValidMcpTool()
+ {
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000")
+ {
+ ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire
+ };
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.NotNull(tool.ToolCallApprovalPolicy);
+ Assert.NotNull(tool.ToolCallApprovalPolicy.GlobalPolicy);
+ Assert.Equal(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval, tool.ToolCallApprovalPolicy.GlobalPolicy);
+ }
+
+ [Fact]
+ public void AsOpenAIResponseTool_WithHostedMcpServerToolWithRequireSpecificApprovalMode_ProducesValidMcpTool()
+ {
+ var alwaysRequireTools = new List { "tool1", "tool2" };
+ var neverRequireTools = new List { "tool3" };
+ var approvalMode = HostedMcpServerToolApprovalMode.RequireSpecific(alwaysRequireTools, neverRequireTools);
+ var mcpTool = new HostedMcpServerTool("test-server", "http://localhost:8000")
+ {
+ ApprovalMode = approvalMode
+ };
+
+ var result = mcpTool.AsOpenAIResponseTool();
+
+ Assert.NotNull(result);
+ var tool = Assert.IsType(result);
+ Assert.NotNull(tool.ToolCallApprovalPolicy);
+ Assert.NotNull(tool.ToolCallApprovalPolicy.CustomPolicy);
+ Assert.NotNull(tool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval);
+ Assert.NotNull(tool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval);
+ Assert.Equal(2, tool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval.ToolNames.Count);
+ Assert.Single(tool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval.ToolNames);
+ Assert.Contains("tool1", tool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval.ToolNames);
+ Assert.Contains("tool2", tool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval.ToolNames);
+ Assert.Contains("tool3", tool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval.ToolNames);
+ }
+
[Fact]
public void AsOpenAIResponseTool_WithUnknownToolType_ReturnsNull()
{
From ea2a2023ec75ff16f4aac46fa1edda6b3710f7c1 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Fri, 24 Oct 2025 17:27:08 +0100
Subject: [PATCH 11/11] Update
src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
Co-authored-by: Stephen Toub
---
.../MicrosoftExtensionsAIResponsesExtensions.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
index 0562a34ca06..ea80ae8e794 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
@@ -29,8 +29,8 @@ public static FunctionTool AsOpenAIResponseTool(this AIFunctionDeclaration funct
/// This method is only able to create s for types
/// it's aware of, namely all of those available from the Microsoft.Extensions.AI.Abstractions library.
///
- public static ResponseTool? AsOpenAIResponseTool(this AITool tool)
- => OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(tool));
+ public static ResponseTool? AsOpenAIResponseTool(this AITool tool) =>
+ OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(tool));
///
/// Creates an OpenAI from a .