Skip to content

Commit bc2258f

Browse files
jozkeeCopilotstephentoubCopilot
authored
HostedToolSearchTool with DeferredTools in the type (#7471)
* Implement HostedToolSearchTool and SearchableAIFunctionDeclaration for tool search support Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Redesign: consolidate tool search into HostedToolSearchTool with DeferredTools/NonDeferredTools - Redesign HostedToolSearchTool with DeferredTools/NonDeferredTools properties - Remove SearchableAIFunctionDeclaration (no longer needed) - Revert DelegatingAIFunctionDeclaration to internal - Update OpenAI provider: use HostedToolSearchTool enable/disable logic for defer_loading - Add ChatOptions parameter to AsOpenAIResponseTool extension method - Use AOT-safe ModelReaderWriter.Read with OpenAIContext.Default - Update API baselines and tests Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Address review feedback: fix O(N²), remove json baseline entry, refactor ToResponseTool signature - Fix O(N²) by finding HostedToolSearchTool once before the tools loop instead of scanning the list for each tool - Remove HostedToolSearchTool from json baseline (experimental types don't need entries) - Refactor ToResponseTool(AITool, ...) to take HostedToolSearchTool? directly instead of extracting from ChatOptions each time - Remove FindToolSearchTool helper method (inlined into callers) Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Extract shared FindToolSearchTool helper to deduplicate lookup code Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Simplify ToResponseTool: add ChatOptions-only overload, make FindToolSearchTool private Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Add unit tests for HostedToolSearchTool JSON serialization and integration test Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Revert to SearchableAIFunctionDeclaration design, remove DeferredTools/NonDeferredTools from HostedToolSearchTool Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/7a29d49e-c422-4fe7-81f4-366bd781b460 * Address review feedback: rename namespaceName to @namespace, add openai-dotnet#1053 comment, add DeferLoadingTools to HostedMcpServerTool Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/08f652ed-169c-43c3-a247-829ebd0b3e4f Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Update tests * revert namespace param rename * Remove Namespace from SearchableAIFunctionDeclaration and DeferLoadingTools from HostedMcpServerTool, add interaction tests - Remove Namespace property and namespaceName parameter from SearchableAIFunctionDeclaration - Remove DeferLoadingTools property from HostedMcpServerTool - Update OpenAIResponsesChatClient to drop namespace patching and MCP defer_loading patching - Update RemoteMCP_DeferLoadingTools integration test to use AsOpenAIResponseTool() + Patch.Set + AsAITool() - Add tool_search_call/tool_search_output assertions to integration test - Add SearchableAIFunctionDeclaration + ApprovalRequiredAIFunction interaction tests - Add FunctionInvokingChatClient test for approval detection through searchable wrapper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove [Experimental] from tool search types and add API baselines Remove [Experimental(AIToolSearch)] attribute from DelegatingAIFunctionDeclaration, SearchableAIFunctionDeclaration, and HostedToolSearchTool. Remove the AIToolSearch diagnostic constant from DiagnosticIds. Add API baseline entries for all three types in Microsoft.Extensions.AI.Abstractions.json. Clean up unused usings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add integration tests for tool search edge cases Add tests verifying that the OpenAI API returns HTTP 400 when HostedToolSearchTool is used without any deferred tools: - UseToolSearch_OnlyToolSearchNoFunctions - UseToolSearch_WithNonDeferredFunctionsOnly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add namespace support to SearchableAIFunctionDeclaration and namespace grouping in OpenAIResponsesChatClient - Add Namespace property and namespaceName parameter to SearchableAIFunctionDeclaration - Add namespaceName parameter to CreateToolSet for bulk namespace assignment - Add namespace grouping logic in OpenAIResponsesChatClient tools loop - Add ToNamespaceResponseTool helper using ModelReaderWriter for AOT-safe JSON - Add namespace unit tests in SearchableAIFunctionDeclarationTests - Add namespace VerbatimHttpHandler tests in OpenAIResponseClientTests - Add UseToolSearch_WithNamespace integration test with tool_search assertions - Add tool_search_call/tool_search_output assertions to existing integration test - Update API baseline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Feedback * Remove Namespace from SearchableAIFunctionDeclaration and namespace grouping from OpenAI provider Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/a0e8d299-9f73-4db3-be63-674f590aa717 Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Revert "Remove Namespace from SearchableAIFunctionDeclaration and namespace grouping from OpenAI provider" This reverts commit 89d8df4. Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Fix CI: replace KeyValuePair deconstruction for net462/netstandard2.0 and restore @namespace param name Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/1783bbad-9689-4b08-bfcc-79988248c2ff Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Add DeferredTools/Namespace on HostedToolSearchTool, support MCP tools - Replace per-tool SearchableAIFunctionDeclaration with collections-based DeferredTools and Namespace properties on HostedToolSearchTool - Support multiple HostedToolSearchTool instances with different/same namespaces (same namespace merges, first-claims wins) - Patch defer_loading on both FunctionTool and McpTool when claimed by a HostedToolSearchTool - Group deferred FunctionTool and McpTool into namespace containers - Widen ToNamespaceResponseTool to accept any ResponseTool - Add AsOpenAIResponseTool ChatOptions parameter for defer context - Delete SearchableAIFunctionDeclaration and its tests - Make DelegatingAIFunctionDeclaration internal (no external consumers) - Add conversion, VerbatimHttpHandler, and integration tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address tool-search reviewer feedback for docs and lookup performance Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/cc5b7c31-3c0f-448e-95c6-2474c2327715 Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Refine HostedToolSearchTool docs to use deferrable tool wording Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/8f9ae606-8c21-4af0-b43d-74a2b463ed33 Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Optimize tool-search lookup precomputation in OpenAI responses client Agent-Logs-Url: https://github.com/dotnet/extensions/sessions/cc5b7c31-3c0f-448e-95c6-2474c2327715 Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com> * Add tests for non-deferrable tools with HostedToolSearchTool Verify that tools like HostedCodeInterpreterTool are unaffected by DeferredTools and namespace grouping: no defer_loading is patched and the tool stays top-level in the request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bring back Experimental * Address test feedback * Drop ChatOptions parameter from AsOpenAIResponseTool(AITool) The ChatOptions? parameter was added so the standalone extension method could honor HostedToolSearchTool.DeferredTools when converting an AITool outside the chat client. In practice, this introduces an O(N^2) hazard if called in a loop (each call rebuilds the ToolSearchLookup) and adds API surface for a niche escape-hatch scenario. The primary path (GetResponseAsync) shares a single lookup and is unaffected. Future per-tool deferral mechanisms (e.g. a SearchableAITool decorator or a DeferLoading bool on AITool) would obsolete this parameter entirely. Callers building ResponseTool lists manually can still set defer_loading via Patch.Set as needed. Removes 11 conversion-level unit tests whose coverage is already provided end-to-end by VerbatimHttpHandler-based tests in OpenAIResponseClientTests.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7723587 commit bc2258f

8 files changed

Lines changed: 1537 additions & 4 deletions

File tree

src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,6 +2687,38 @@
26872687
}
26882688
]
26892689
},
2690+
{
2691+
"Type": "class Microsoft.Extensions.AI.HostedToolSearchTool : Microsoft.Extensions.AI.AITool",
2692+
"Stage": "Experimental",
2693+
"Methods": [
2694+
{
2695+
"Member": "Microsoft.Extensions.AI.HostedToolSearchTool.HostedToolSearchTool();",
2696+
"Stage": "Experimental"
2697+
},
2698+
{
2699+
"Member": "Microsoft.Extensions.AI.HostedToolSearchTool.HostedToolSearchTool(System.Collections.Generic.IReadOnlyDictionary<string, object?>? additionalProperties);",
2700+
"Stage": "Experimental"
2701+
}
2702+
],
2703+
"Properties": [
2704+
{
2705+
"Member": "override System.Collections.Generic.IReadOnlyDictionary<string, object?> Microsoft.Extensions.AI.HostedToolSearchTool.AdditionalProperties { get; }",
2706+
"Stage": "Experimental"
2707+
},
2708+
{
2709+
"Member": "System.Collections.Generic.IList<string>? Microsoft.Extensions.AI.HostedToolSearchTool.DeferredTools { get; set; }",
2710+
"Stage": "Experimental"
2711+
},
2712+
{
2713+
"Member": "override string Microsoft.Extensions.AI.HostedToolSearchTool.Name { get; }",
2714+
"Stage": "Experimental"
2715+
},
2716+
{
2717+
"Member": "string? Microsoft.Extensions.AI.HostedToolSearchTool.Namespace { get; set; }",
2718+
"Stage": "Experimental"
2719+
}
2720+
]
2721+
},
26902722
{
26912723
"Type": "sealed class Microsoft.Extensions.AI.HostedVectorStoreContent : Microsoft.Extensions.AI.AIContent",
26922724
"Stage": "Stable",
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Shared.DiagnosticIds;
7+
8+
namespace Microsoft.Extensions.AI;
9+
10+
/// <summary>
11+
/// Represents a hosted tool that can be specified to an AI service to enable it to search for and selectively load tool definitions on demand.
12+
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// This tool does not itself implement tool search. It is a marker that can be used to inform a service
16+
/// that tool search should be enabled. When included, deferred tools are not placed into the model's context
17+
/// upfront; instead, the model invokes tool search to surface relevant tools on demand, reducing the input
18+
/// tokens consumed by tool definitions the model doesn't need.
19+
/// </para>
20+
/// <para>
21+
/// By default, when a <see cref="HostedToolSearchTool"/> is present in the tools list, all other deferrable tools
22+
/// are treated as having deferred loading enabled. Use <see cref="DeferredTools"/> to control which tools have deferred loading
23+
/// on a per-tool basis.
24+
/// </para>
25+
/// </remarks>
26+
[Experimental(DiagnosticIds.Experiments.AIToolSearch, UrlFormat = DiagnosticIds.UrlFormat)]
27+
public class HostedToolSearchTool : AITool
28+
{
29+
/// <summary>Any additional properties associated with the tool.</summary>
30+
private IReadOnlyDictionary<string, object?>? _additionalProperties;
31+
32+
/// <summary>Initializes a new instance of the <see cref="HostedToolSearchTool"/> class.</summary>
33+
public HostedToolSearchTool()
34+
{
35+
}
36+
37+
/// <summary>Initializes a new instance of the <see cref="HostedToolSearchTool"/> class.</summary>
38+
/// <param name="additionalProperties">Any additional properties associated with the tool.</param>
39+
public HostedToolSearchTool(IReadOnlyDictionary<string, object?>? additionalProperties)
40+
{
41+
_additionalProperties = additionalProperties;
42+
}
43+
44+
/// <inheritdoc />
45+
public override string Name => "tool_search";
46+
47+
/// <inheritdoc />
48+
public override IReadOnlyDictionary<string, object?> AdditionalProperties => _additionalProperties ?? base.AdditionalProperties;
49+
50+
/// <summary>
51+
/// Gets or sets the list of tool names for which deferred loading should be enabled.
52+
/// </summary>
53+
/// <remarks>
54+
/// <para>
55+
/// The default value is <see langword="null"/>, which enables deferred loading for all deferrable tools in the tools list.
56+
/// </para>
57+
/// <para>
58+
/// When non-null, only deferrable tools whose names appear in this list will have deferred loading enabled.
59+
/// </para>
60+
/// </remarks>
61+
public IList<string>? DeferredTools { get; set; }
62+
63+
/// <summary>
64+
/// Gets or sets the namespace name under which deferred tools should be grouped.
65+
/// </summary>
66+
/// <remarks>
67+
/// <para>
68+
/// When non-null, all deferred tools are wrapped inside a <c>{"type":"namespace","name":"..."}</c>
69+
/// container. Non-deferred tools remain as top-level tools.
70+
/// </para>
71+
/// <para>
72+
/// When <see langword="null"/> (the default), deferred tools are sent as top-level tools
73+
/// with <c>defer_loading</c> set individually.
74+
/// </para>
75+
/// </remarks>
76+
public string? Namespace { get; set; }
77+
}

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Threading.Tasks;
1818
using Microsoft.Shared.DiagnosticIds;
1919
using Microsoft.Shared.Diagnostics;
20+
using OpenAI;
2021
using OpenAI.Responses;
2122

2223
#pragma warning disable S1226 // Method parameters, caught exceptions and foreach variables' initial values should not be ignored
@@ -701,15 +702,26 @@ private static bool IsStoredOutputDisabled(CreateResponseOptions? options, Respo
701702
(response is not null && response.Patch.TryGetValue("$.store"u8, out bool store) && !store);
702703
#pragma warning restore SCME0001
703704

704-
internal static ResponseTool? ToResponseTool(AITool tool, ChatOptions? options = null)
705+
internal static ResponseTool? ToResponseTool(AITool tool, ChatOptions? options = null, ToolSearchLookup? toolSearchLookup = null)
705706
{
706707
switch (tool)
707708
{
708709
case ResponseToolAITool rtat:
709710
return rtat.Tool;
710711

711712
case AIFunctionDeclaration aiFunction:
712-
return ToResponseTool(aiFunction, options);
713+
var functionTool = ToResponseTool(aiFunction, options);
714+
if ((toolSearchLookup ??= ToolSearchLookup.Create(options?.Tools)).IsDeferred(aiFunction.Name))
715+
{
716+
functionTool.Patch.Set("$.defer_loading"u8, "true"u8);
717+
}
718+
719+
return functionTool;
720+
721+
case HostedToolSearchTool:
722+
// Workaround: The OpenAI .NET SDK doesn't yet expose a ToolSearchTool type.
723+
// See https://github.com/openai/openai-dotnet/issues/1053
724+
return ModelReaderWriter.Read<ResponseTool>(BinaryData.FromString("""{"type": "tool_search"}"""), ModelReaderWriterOptions.Json, OpenAIContext.Default)!;
713725

714726
case HostedWebSearchTool webSearchTool:
715727
return new WebSearchTool
@@ -821,6 +833,11 @@ private static bool IsStoredOutputDisabled(CreateResponseOptions? options, Respo
821833
break;
822834
}
823835

836+
if ((toolSearchLookup ??= ToolSearchLookup.Create(options?.Tools)).IsDeferred(mcpTool.ServerName))
837+
{
838+
responsesMcpTool.Patch.Set("$.defer_loading"u8, "true"u8);
839+
}
840+
824841
return responsesMcpTool;
825842

826843
default:
@@ -843,6 +860,34 @@ internal static FunctionTool ToResponseTool(AIFunctionDeclaration aiFunction, Ch
843860
};
844861
}
845862

863+
/// <summary>
864+
/// Builds a <c>{"type":"namespace"}</c> <see cref="ResponseTool"/> from a name and set of tools.
865+
/// The OpenAI .NET SDK doesn't expose a NamespaceTool type, so we construct the JSON manually.
866+
/// </summary>
867+
internal static ResponseTool ToNamespaceResponseTool(string name, IEnumerable<ResponseTool> namespacedTools)
868+
{
869+
using var stream = new System.IO.MemoryStream();
870+
using (var writer = new Utf8JsonWriter(stream))
871+
{
872+
writer.WriteStartObject();
873+
writer.WriteString("type"u8, "namespace"u8);
874+
writer.WriteString("name"u8, name);
875+
876+
writer.WriteStartArray("tools"u8);
877+
foreach (var namespacedTool in namespacedTools)
878+
{
879+
var toolData = ModelReaderWriter.Write(namespacedTool, ModelReaderWriterOptions.Json, OpenAIContext.Default);
880+
using var doc = JsonDocument.Parse(toolData);
881+
doc.RootElement.WriteTo(writer);
882+
}
883+
884+
writer.WriteEndArray();
885+
writer.WriteEndObject();
886+
}
887+
888+
return ModelReaderWriter.Read<ResponseTool>(BinaryData.FromBytes(stream.ToArray()), ModelReaderWriterOptions.Json, OpenAIContext.Default)!;
889+
}
890+
846891
/// <summary>Creates a <see cref="ChatRole"/> from a <see cref="MessageRole"/>.</summary>
847892
private static ChatRole AsChatRole(MessageRole? role) =>
848893
role switch
@@ -926,14 +971,45 @@ private CreateResponseOptions AsCreateResponseOptions(ChatOptions? options, out
926971
// Populate tools if there are any.
927972
if (options.Tools is { Count: > 0 } tools)
928973
{
974+
ToolSearchLookup toolSearchLookup = ToolSearchLookup.Create(tools);
975+
Dictionary<string, List<ResponseTool>>? namespaceGroups = null;
976+
929977
foreach (AITool tool in tools)
930978
{
931-
if (ToResponseTool(tool, options) is { } responseTool)
979+
if (ToResponseTool(tool, options, toolSearchLookup) is { } responseTool)
932980
{
981+
// When a namespaced HostedToolSearchTool claims this deferred tool,
982+
// collect it for later wrapping in a namespace container.
983+
string? responseToolName = responseTool is FunctionTool ft ? ft.FunctionName
984+
: responseTool is McpTool mcp ? mcp.ServerLabel
985+
: null;
986+
987+
if (responseToolName is not null
988+
&& toolSearchLookup.GetNamespace(responseToolName) is { } ns)
989+
{
990+
namespaceGroups ??= new(StringComparer.Ordinal);
991+
if (!namespaceGroups.TryGetValue(ns, out var group))
992+
{
993+
group = new();
994+
namespaceGroups[ns] = group;
995+
}
996+
997+
group.Add(responseTool);
998+
continue;
999+
}
1000+
9331001
result.Tools.Add(responseTool);
9341002
}
9351003
}
9361004

1005+
if (namespaceGroups is not null)
1006+
{
1007+
foreach (KeyValuePair<string, List<ResponseTool>> kvp in namespaceGroups)
1008+
{
1009+
result.Tools.Add(ToNamespaceResponseTool(kvp.Key, kvp.Value));
1010+
}
1011+
}
1012+
9371013
if (result.Tools.Count > 0)
9381014
{
9391015
result.ParallelToolCallsEnabled ??= options.AllowMultipleToolCalls;
@@ -969,6 +1045,98 @@ private CreateResponseOptions AsCreateResponseOptions(ChatOptions? options, out
9691045
return result;
9701046
}
9711047

1048+
internal sealed class ToolSearchLookup
1049+
{
1050+
private static readonly ToolSearchLookup _empty = new(deferAll: false, deferredToolNames: [], namespacedToolNames: []);
1051+
private readonly bool _deferAll;
1052+
private readonly HashSet<string> _deferredToolNames;
1053+
private readonly Dictionary<string, string> _namespacedToolNames;
1054+
1055+
private ToolSearchLookup(bool deferAll, HashSet<string> deferredToolNames, Dictionary<string, string> namespacedToolNames)
1056+
{
1057+
_deferAll = deferAll;
1058+
_deferredToolNames = deferredToolNames;
1059+
_namespacedToolNames = namespacedToolNames;
1060+
}
1061+
1062+
public static ToolSearchLookup Create(IList<AITool>? tools)
1063+
{
1064+
if (tools is not { Count: > 0 })
1065+
{
1066+
return _empty;
1067+
}
1068+
1069+
HashSet<string> functionAndMcpToolNames = new(
1070+
tools.Select(
1071+
static tool => tool switch
1072+
{
1073+
AIFunctionDeclaration aiFunction => aiFunction.Name,
1074+
HostedMcpServerTool mcpTool => mcpTool.ServerName,
1075+
_ => null,
1076+
})
1077+
.OfType<string>(),
1078+
StringComparer.Ordinal);
1079+
1080+
if (functionAndMcpToolNames.Count == 0)
1081+
{
1082+
return _empty;
1083+
}
1084+
1085+
bool deferAll = false;
1086+
HashSet<string> deferredToolNames = new(StringComparer.Ordinal);
1087+
Dictionary<string, string> namespacedToolNames = new(StringComparer.Ordinal);
1088+
HashSet<string> unclaimedToolNames = new(functionAndMcpToolNames, StringComparer.Ordinal);
1089+
1090+
foreach (AITool tool in tools)
1091+
{
1092+
if (tool is not HostedToolSearchTool toolSearch)
1093+
{
1094+
continue;
1095+
}
1096+
1097+
if (toolSearch.DeferredTools is not { } deferredTools)
1098+
{
1099+
deferAll = true;
1100+
deferredToolNames.UnionWith(functionAndMcpToolNames);
1101+
1102+
if (toolSearch.Namespace is { } ns && unclaimedToolNames.Count > 0)
1103+
{
1104+
foreach (string toolName in unclaimedToolNames)
1105+
{
1106+
namespacedToolNames[toolName] = ns;
1107+
}
1108+
1109+
unclaimedToolNames.Clear();
1110+
}
1111+
1112+
continue;
1113+
}
1114+
1115+
foreach (string deferredTool in deferredTools)
1116+
{
1117+
if (!functionAndMcpToolNames.Contains(deferredTool))
1118+
{
1119+
continue;
1120+
}
1121+
1122+
_ = deferredToolNames.Add(deferredTool);
1123+
if (toolSearch.Namespace is { } ns && unclaimedToolNames.Remove(deferredTool))
1124+
{
1125+
namespacedToolNames[deferredTool] = ns;
1126+
}
1127+
}
1128+
}
1129+
1130+
return new(deferAll, deferredToolNames, namespacedToolNames);
1131+
}
1132+
1133+
public bool IsDeferred(string toolName) =>
1134+
_deferAll || _deferredToolNames.Contains(toolName);
1135+
1136+
public string? GetNamespace(string toolName) =>
1137+
_namespacedToolNames.TryGetValue(toolName, out string? ns) ? ns : null;
1138+
}
1139+
9721140
internal static ResponseTextFormat? ToOpenAIResponseTextFormat(ChatResponseFormat? format, ChatOptions? options = null) =>
9731141
format switch
9741142
{

src/Shared/DiagnosticIds/DiagnosticIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ internal static class Experiments
5858
internal const string AIResponseContinuations = AIExperiments;
5959
internal const string AICodeInterpreter = AIExperiments;
6060
internal const string AIWebSearch = AIExperiments;
61+
internal const string AIToolSearch = AIExperiments;
6162
internal const string AIRealTime = AIExperiments;
6263
internal const string AIFiles = AIExperiments;
6364

0 commit comments

Comments
 (0)