diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs index 0878ad8bcb..8e9ca91267 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs @@ -29,6 +29,7 @@ internal static class ChatClientAgentRunOptionsConverter Model = request.Model, ToolChoice = request.ToolChoice?.ToChatToolMode(), Tools = request.Tools is { Count: > 0 } tools ? tools.Select(x => x.ToAITool()).ToList() : null, + WebSearchOptions = request.WebSearchOptions, }; private static ChatResponseFormat ToChatResponseFormat(this ResponseFormat responseFormat) diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionRequestInfo.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionRequestInfo.cs index e345f18404..299dd4b382 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionRequestInfo.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionRequestInfo.cs @@ -84,4 +84,9 @@ public sealed class OpenAIChatCompletionRequestInfo /// Gets or sets the tools supplied on the request, if any. /// public IReadOnlyList? Tools { get; set; } + + /// + /// Gets or sets the web search options (web_search_options) supplied on the request, if any. + /// + public object? WebSearchOptions { get; set; } } diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionsMapOptions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionsMapOptions.cs index ba7f2f33d4..2aa33d64b1 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionsMapOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIChatCompletionsMapOptions.cs @@ -109,6 +109,11 @@ public sealed class OpenAIChatCompletionsMapOptions LocalAdd("tool_choice"); } + if (request.WebSearchOptions is not null) + { + LocalAdd("web_search_options"); + } + if (unsupported is not null) { throw new NotSupportedException( diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIMapOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIMapOptionsTests.cs index b0ce53eaa0..67c3683ec6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIMapOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIMapOptionsTests.cs @@ -181,6 +181,25 @@ public async Task ChatCompletions_DefaultEndpoint_RejectsRequestWithSettingsAsyn Assert.Contains("invalid_request_error", body, StringComparison.Ordinal); } + [Fact] + public async Task ChatCompletions_DefaultEndpoint_RejectsWebSearchOptionsAsync() + { + // Arrange + using var app = await CreateChatCompletionsServerAsync("reject-agent", mapOptions: null); + HttpClient client = GetClient(app); + + // Act + HttpResponseMessage response = await client.PostAsync( + new Uri("/reject-agent/v1/chat/completions", UriKind.Relative), + new StringContent("""{"model":"myModel","messages":[{"role":"user","content":"hello"}],"web_search_options":{}}""", Encoding.UTF8, "application/json")); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + string body = await response.Content.ReadAsStringAsync(); + Assert.Contains("web_search_options", body, StringComparison.Ordinal); + Assert.Contains("invalid_request_error", body, StringComparison.Ordinal); + } + [Fact] public async Task ChatCompletions_ConfiguredEndpoint_HonorsRequestSettingsAsync() {