First version of SearXNG-based Websearch and a tool to read web page content.

This commit is contained in:
Peer Schütt 2026-04-13 13:53:24 +02:00
parent 9dfae573a8
commit ad3d9dc71f
21 changed files with 1021 additions and 36 deletions

View File

@ -3,6 +3,7 @@ using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
@ -257,7 +258,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected Task SelectedToolIdsChanged(HashSet<string> updatedToolIds)
{
this.selectedToolIds = updatedToolIds;
this.selectedToolIds = ToolSelectionRules.NormalizeSelection(updatedToolIds);
return Task.CompletedTask;
}
@ -309,7 +310,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.chatThread.SelectedProvider = this.providerSettings.Id;
this.chatThread.RuntimeComponent = this.Component;
this.chatThread.RuntimeSelectedToolIds = this.SettingsManager.IsToolSelectionVisible(this.Component)
? [..this.selectedToolIds]
? ToolSelectionRules.NormalizeSelection(this.selectedToolIds)
: [];
}

View File

@ -2632,15 +2632,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T176751696"
-- This tool still needs to be configured.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "This tool still needs to be configured."
-- Tools
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2499909372"] = "Tools"
-- Missing required settings: {0}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Missing required settings: {0}"
-- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name"
-- Tool Settings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Tool Settings"
-- State
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T502047894"] = "State"
@ -2743,9 +2743,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T907403808"] =
-- Choose which tools should be preselected for new chats.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T948842182"] = "Choose which tools should be preselected for new chats."
-- This tool is currently required because Web Search is enabled.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1351725609"] = "This tool is currently required because Web Search is enabled."
-- Tool changes are locked while a response is running. Your current selection is shown below and applies again from the next message once the run is finished.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1688023907"] = "Tool changes are locked while a response is running. Your current selection is shown below and applies again from the next message once the run is finished."
-- Enabling this tool also enables Read Web Page.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3023833839"] = "Enabling this tool also enables Read Web Page."
-- Required settings are missing. Configure this tool before enabling it.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3119156561"] = "Required settings are missing. Configure this tool before enabling it."
@ -6841,17 +6847,17 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4174900468"] = "Sources pro
-- Sources provided by the AI
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4261248356"] = "Sources provided by the AI"
-- Current Weather
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T1597702905"] = "Current Weather"
-- Current Weather", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); public string GetDescription() => I18N.I.T("Use this demo tool to retrieve the current weather for a given city and state. It is primarily meant to demonstrate tool calling and tool settings in AI Studio.", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "demoLabel" => I18N.I.T("Demo Label", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "demoLabel" => I18N.I.T("Required demo setting for validating tool settings in tests. It does not affect the weather result.", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty("city", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty("state", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty("unit", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not ("celsius" or "fahrenheit
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T1597702905"] = "Current Weather\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); public string GetDescription() => I18N.I.T(\"Use this demo tool to retrieve the current weather for a given city and state. It is primarily meant to demonstrate tool calling and tool settings in AI Studio.\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"demoLabel\" => I18N.I.T(\"Demo Label\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"demoLabel\" => I18N.I.T(\"Required demo setting for validating tool settings in tests. It does not affect the weather result.\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty(\"city\", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty(\"state\", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty(\"unit\", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not (\"celsius\" or \"fahrenheit"
-- Use this demo tool to retrieve the current weather for a given city and state. It is primarily meant to demonstrate tool calling and tool settings in AI Studio.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T2152408159"] = "Use this demo tool to retrieve the current weather for a given city and state. It is primarily meant to demonstrate tool calling and tool settings in AI Studio."
-- Use this demo tool to retrieve the current weather for a given city and state. It is primarily meant to demonstrate tool calling and tool settings in AI Studio.", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "demoLabel" => I18N.I.T("Demo Label", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "demoLabel" => I18N.I.T("Required demo setting for validating tool settings in tests. It does not affect the weather result.", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty("city", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty("state", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty("unit", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not ("celsius" or "fahrenheit
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T2152408159"] = "Use this demo tool to retrieve the current weather for a given city and state. It is primarily meant to demonstrate tool calling and tool settings in AI Studio.\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"demoLabel\" => I18N.I.T(\"Demo Label\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"demoLabel\" => I18N.I.T(\"Required demo setting for validating tool settings in tests. It does not affect the weather result.\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty(\"city\", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty(\"state\", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty(\"unit\", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not (\"celsius\" or \"fahrenheit"
-- Required demo setting for validating tool settings in tests. It does not affect the weather result.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T25380508"] = "Required demo setting for validating tool settings in tests. It does not affect the weather result."
-- Required demo setting for validating tool settings in tests. It does not affect the weather result.", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty("city", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty("state", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty("unit", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not ("celsius" or "fahrenheit
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T25380508"] = "Required demo setting for validating tool settings in tests. It does not affect the weather result.\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty(\"city\", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty(\"state\", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty(\"unit\", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not (\"celsius\" or \"fahrenheit"
-- Demo Label
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T3346467484"] = "Demo Label"
-- Demo Label", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "demoLabel" => I18N.I.T("Required demo setting for validating tool settings in tests. It does not affect the weather result.", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty("city", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty("state", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty("unit", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not ("celsius" or "fahrenheit
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::GETCURRENTWEATHERTOOL::T3346467484"] = "Demo Label\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"demoLabel\" => I18N.I.T(\"Required demo setting for validating tool settings in tests. It does not affect the weather result.\", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)), }; public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var city = arguments.TryGetProperty(\"city\", out var cityValue) ? cityValue.GetString() ?? string.Empty : string.Empty; var state = arguments.TryGetProperty(\"state\", out var stateValue) ? stateValue.GetString() ?? string.Empty : string.Empty; var unit = arguments.TryGetProperty(\"unit\", out var unitValue) ? unitValue.GetString() ?? string.Empty : string.Empty; if (unit is not (\"celsius\" or \"fahrenheit"
-- Tool
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Tool"
@ -6859,6 +6865,87 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T35170
-- Tool description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description"
-- Optional HTTP timeout for loading a web page in seconds.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::READWEBPAGETOOL::T1169117578"] = "Optional HTTP timeout for loading a web page in seconds.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Optional global truncation limit for extracted Markdown returned to the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url"
-- Load a single web page, extract its main HTML content, and return Markdown plus metadata for the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Maximum Content Characters", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for loading a web page in seconds.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::READWEBPAGETOOL::T2215866777"] = "Load a single web page, extract its main HTML content, and return Markdown plus metadata for the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Maximum Content Characters\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for loading a web page in seconds.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Optional global truncation limit for extracted Markdown returned to the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url"
-- Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::READWEBPAGETOOL::T2765372972"] = "Optional global truncation limit for extracted Markdown returned to the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url"
-- Maximum Content Characters", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for loading a web page in seconds.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::READWEBPAGETOOL::T2860394705"] = "Maximum Content Characters\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for loading a web page in seconds.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Optional global truncation limit for extracted Markdown returned to the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url"
-- Timeout Seconds", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Maximum Content Characters", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for loading a web page in seconds.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::READWEBPAGETOOL::T3510104271"] = "Timeout Seconds\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Maximum Content Characters\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for loading a web page in seconds.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Optional global truncation limit for extracted Markdown returned to the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url"
-- Read Web Page", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); public string GetDescription() => I18N.I.T("Load a single web page, extract its main HTML content, and return Markdown plus metadata for the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Maximum Content Characters", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for loading a web page in seconds.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), "maxContentCharacters" => I18N.I.T("Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::READWEBPAGETOOL::T3517638643"] = "Read Web Page\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); public string GetDescription() => I18N.I.T(\"Load a single web page, extract its main HTML content, and return Markdown plus metadata for the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Maximum Content Characters\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for loading a web page in seconds.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), \"maxContentCharacters\" => I18N.I.T(\"Optional global truncation limit for extracted Markdown returned to the model.\", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url"
-- Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T1254458306"] = "Default Categories\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T1327402904"] = "Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T1401266403"] = "Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T1539252250"] = "Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Search the web with a configured SearXNG instance and return structured JSON results for the model. When deeper content is needed, use Read Web Page on relevant result URLs.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("SearXNG URL", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Default Language", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Default Safe Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T1563842161"] = "Search the web with a configured SearXNG instance and return structured JSON results for the model. When deeper content is needed, use Read Web Page on relevant result URLs.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"SearXNG URL\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Default Language\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Default Safe Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Default Categories\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T186659624"] = "Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Default categories and default engines cannot both be set for the web search tool.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool))); var defaultLimit = ReadOptionalPositiveIntSetting(context.SettingsValues, "maxResults
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2087438861"] = "Default categories and default engines cannot both be set for the web search tool.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool))); var defaultLimit = ReadOptionalPositiveIntSetting(context.SettingsValues, \"maxResults"
-- The configured SearXNG URL must start with http:// or https://.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } var basePath = parsedUri.AbsolutePath.TrimEnd('/'); if (basePath.EndsWith("/search", StringComparison.OrdinalIgnoreCase)) basePath = basePath[..^"/search".Length]; var normalizedPath = $"{basePath}/search"; var builder = new UriBuilder(parsedUri) { Path = normalizedPath, Query = string.Empty, Fragment = string.Empty, }; searchUri = builder.Uri; return true; } private static Uri BuildRequestUri(Uri searchUri, IEnumerable<KeyValuePair<string, string>> queryParameters) { var builder = new StringBuilder(); foreach (var parameter in queryParameters) { if (builder.Length > 0) builder.Append('&'); builder.Append(WebUtility.UrlEncode(parameter.Key)); builder.Append('='); builder.Append(WebUtility.UrlEncode(parameter.Value)); } var uriBuilder = new UriBuilder(searchUri) { Query = builder.ToString(), }; return uriBuilder.Uri; } private static async Task<HttpResponseMessage> SendAsync( HttpClient httpClient, HttpRequestMessage request, CancellationToken requestToken, int timeoutSeconds, CancellationToken callerToken) { try { return await httpClient.SendAsync(request, requestToken); } catch (OperationCanceledException) when (!callerToken.IsCancellationRequested) { throw new TimeoutException($"The SearXNG request timed out after {timeoutSeconds} seconds.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T212620884"] = "The configured SearXNG URL must start with http:// or https://.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } var basePath = parsedUri.AbsolutePath.TrimEnd('/'); if (basePath.EndsWith(\"/search\", StringComparison.OrdinalIgnoreCase)) basePath = basePath[..^\"/search\".Length]; var normalizedPath = $\"{basePath}/search\"; var builder = new UriBuilder(parsedUri) { Path = normalizedPath, Query = string.Empty, Fragment = string.Empty, }; searchUri = builder.Uri; return true; } private static Uri BuildRequestUri(Uri searchUri, IEnumerable<KeyValuePair<string, string>> queryParameters) { var builder = new StringBuilder(); foreach (var parameter in queryParameters) { if (builder.Length > 0) builder.Append('&'); builder.Append(WebUtility.UrlEncode(parameter.Key)); builder.Append('='); builder.Append(WebUtility.UrlEncode(parameter.Value)); } var uriBuilder = new UriBuilder(searchUri) { Query = builder.ToString(), }; return uriBuilder.Uri; } private static async Task<HttpResponseMessage> SendAsync( HttpClient httpClient, HttpRequestMessage request, CancellationToken requestToken, int timeoutSeconds, CancellationToken callerToken) { try { return await httpClient.SendAsync(request, requestToken); } catch (OperationCanceledException) when (!callerToken.IsCancellationRequested) { throw new TimeoutException($\"The SearXNG request timed out after {timeoutSeconds} seconds."
-- Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2170342710"] = "Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- A SearXNG URL is required.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } if (!Uri.TryCreate(rawUrl.Trim(), UriKind.Absolute, out var parsedUri)) { error = I18N.I.T("The configured SearXNG URL is not a valid absolute URL.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } if (parsedUri.Scheme is not ("http" or "https
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2420801571"] = "A SearXNG URL is required.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } if (!Uri.TryCreate(rawUrl.Trim(), UriKind.Absolute, out var parsedUri)) { error = I18N.I.T(\"The configured SearXNG URL is not a valid absolute URL.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } if (parsedUri.Scheme is not (\"http\" or \"https"
-- Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2435794648"] = "Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Default categories and default engines cannot both be set for the web search tool.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxResults", out _, out var maxResultsError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = maxResultsError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { context.SettingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out var searchUri, out var uriError); if (!isValidBaseUrl) throw new InvalidOperationException(uriError); var query = ReadRequiredString(arguments, "query
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2471183191"] = "Default categories and default engines cannot both be set for the web search tool.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxResults\", out _, out var maxResultsError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = maxResultsError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } return Task.FromResult<ToolConfigurationState?>(null); } public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { context.SettingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out var searchUri, out var uriError); if (!isValidBaseUrl) throw new InvalidOperationException(uriError); var query = ReadRequiredString(arguments, \"query"
-- Web Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetDescription() => I18N.I.T("Search the web with a configured SearXNG instance and return structured JSON results for the model. When deeper content is needed, use Read Web Page on relevant result URLs.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("SearXNG URL", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Default Language", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Default Safe Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2590432104"] = "Web Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetDescription() => I18N.I.T(\"Search the web with a configured SearXNG instance and return structured JSON results for the model. When deeper content is needed, use Read Web Page on relevant result URLs.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"SearXNG URL\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Default Language\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Default Safe Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Default Categories\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- SearXNG URL", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Default Language", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Default Safe Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2707478507"] = "SearXNG URL\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Default Language\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Default Safe Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Default Categories\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2911071656"] = "Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T2953585467"] = "Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T3332435511"] = "Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- The configured SearXNG URL is not a valid absolute URL.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } if (parsedUri.Scheme is not ("http" or "https
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T371406570"] = "The configured SearXNG URL is not a valid absolute URL.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); return false; } if (parsedUri.Scheme is not (\"http\" or \"https"
-- Default Safe Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T3780386928"] = "Default Safe Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Default Categories\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Default Language", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Default Safe Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { "baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T54221234"] = "Default Language\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Default Safe Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Default Categories\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Default Engines\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Maximum Results\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Timeout Seconds\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch { \"baseUrl\" => I18N.I.T(\"Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), "timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue("baseUrl", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::SEARXNGWEBSEARCHTOOL::T54269506"] = "Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultLanguage\" => I18N.I.T(\"Optional fallback language code when the model does not provide a language.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultSafeSearch\" => I18N.I.T(\"Optional safe search policy sent to SearXNG when configured.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultCategories\" => I18N.I.T(\"Optional comma-separated default categories. Do not set this together with default engines.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"defaultEngines\" => I18N.I.T(\"Optional comma-separated default engines. Do not set this together with default categories.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"maxResults\" => I18N.I.T(\"Optional default maximum number of results returned to the model when the model does not provide a limit.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), \"timeoutSeconds\" => I18N.I.T(\"Optional HTTP timeout for the search request in seconds.\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), _ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)), }; public Task<ToolConfigurationState?> ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary<string, string> settingsValues, CancellationToken token = default) { settingsValues.TryGetValue(\"baseUrl\", out var baseUrl); var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError); if (!isValidBaseUrl) { return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories"
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation"

View File

@ -3,6 +3,7 @@ using AIStudio.Dialogs;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@ -92,7 +93,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// Get the preselected chat template:
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
this.selectedToolIds = this.SettingsManager.GetDefaultToolIds(Tools.Components.CHAT);
this.selectedToolIds = ToolSelectionRules.NormalizeSelection(this.SettingsManager.GetDefaultToolIds(Tools.Components.CHAT));
// Apply template's file attachments, if any:
foreach (var attachment in this.currentChatTemplate.FileAttachments)
@ -610,7 +611,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
{
this.StateHasChanged();
this.ChatThread!.RuntimeComponent = Tools.Components.CHAT;
this.ChatThread.RuntimeSelectedToolIds = [..this.selectedToolIds];
this.ChatThread.RuntimeSelectedToolIds = ToolSelectionRules.NormalizeSelection(this.selectedToolIds);
// Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire
@ -643,7 +644,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private Task SelectedToolIdsChanged(HashSet<string> updatedToolIds)
{
this.selectedToolIds = updatedToolIds;
this.selectedToolIds = ToolSelectionRules.NormalizeSelection(updatedToolIds);
return Task.CompletedTask;
}

View File

@ -1,7 +1,7 @@
@using AIStudio.Tools.ToolCallingSystem
@inherits SettingsPanelBase
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Build" HeaderText="@T("Tools")">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Build" HeaderText="@T("Tool Settings")">
<MudText Typo="Typo.body1" Class="mb-4">
@T("Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings.")
</MudText>

View File

@ -34,6 +34,7 @@ public partial class SettingsPanelTools : SettingsPanelBase
private string GetConfigurationTooltip(ToolCatalogItem item) => item.ConfigurationState.MissingRequiredFields.Count switch
{
_ when !string.IsNullOrWhiteSpace(item.ConfigurationState.Message) => item.ConfigurationState.Message,
0 => this.T("This tool still needs to be configured."),
_ => string.Format(this.T("Missing required settings: {0}"), string.Join(", ", item.ConfigurationState.MissingRequiredFields.Select(fieldName => this.GetFieldDisplayName(item, fieldName))))
};

View File

@ -39,5 +39,5 @@ public partial class ToolDefaultsConfiguration : MSGComponentBase
private HashSet<string> GetSelectedValues() => this.SettingsManager.GetDefaultToolIds(this.Component);
private void UpdateSelection(HashSet<string> values) => this.SettingsManager.ConfigurationData.Tools.DefaultToolIdsByComponent[this.Component.ToString()] = [..values];
private void UpdateSelection(HashSet<string> values) => this.SettingsManager.ConfigurationData.Tools.DefaultToolIdsByComponent[this.Component.ToString()] = [..ToolSelectionRules.NormalizeSelection(values)];
}

View File

@ -39,10 +39,11 @@
{
var isSelected = this.SelectedToolIds.Contains(item.Definition.Id);
var isConfigured = item.ConfigurationState.IsConfigured;
var dependencyHint = this.GetDependencyHint(item.Definition.Id);
<MudPaper Class="pa-2 mb-2 border rounded-lg">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudSwitch T="bool" Color="Color.Primary" Value="@isSelected" ValueChanged="@(value => this.ChangeSelection(item.Definition.Id, value))" Disabled="@(!isConfigured || this.Disabled || !this.SupportsTools)" />
<MudSwitch T="bool" Color="Color.Primary" Value="@isSelected" ValueChanged="@(value => this.ChangeSelection(item.Definition.Id, value))" Disabled="@(!isConfigured || this.Disabled || !this.SupportsTools || this.IsSelectionLockedByDependency(item.Definition.Id))" />
<MudIcon Icon="@item.Implementation.Icon" Color="Color.Info" />
<MudTooltip Text="@item.Implementation.GetDescription()">
<MudText Typo="Typo.body1">@item.Implementation.GetDisplayName()</MudText>
@ -52,7 +53,11 @@
</MudStack>
@if (!isConfigured)
{
<MudText Typo="Typo.caption" Color="Color.Warning">@T("Required settings are missing. Configure this tool before enabling it.")</MudText>
<MudText Typo="Typo.caption" Color="Color.Warning">@(string.IsNullOrWhiteSpace(item.ConfigurationState.Message) ? T("Required settings are missing. Configure this tool before enabling it.") : item.ConfigurationState.Message)</MudText>
}
@if (!string.IsNullOrWhiteSpace(dependencyHint))
{
<MudText Typo="Typo.caption" Color="Color.Info">@dependencyHint</MudText>
}
</MudPaper>
}

View File

@ -37,6 +37,12 @@ public partial class ToolSelection : MSGComponentBase
private bool showSelection;
private IReadOnlyList<ToolCatalogItem> catalog = [];
protected override void OnParametersSet()
{
this.SelectedToolIds = ToolSelectionRules.NormalizeSelection(this.SelectedToolIds);
base.OnParametersSet();
}
private bool SupportsTools =>
this.LLMProvider != AIStudio.Settings.Provider.NONE &&
this.LLMProvider.GetModelCapabilities().Contains(Capability.CHAT_COMPLETION_API) &&
@ -59,10 +65,24 @@ public partial class ToolSelection : MSGComponentBase
else
updated.Remove(toolId);
updated = ToolSelectionRules.NormalizeSelection(updated);
this.SelectedToolIds = updated;
await this.SelectedToolIdsChanged.InvokeAsync(updated);
}
private bool IsSelectionLockedByDependency(string toolId) => ToolSelectionRules.IsRequiredBySelectedTools(toolId, this.SelectedToolIds);
private string? GetDependencyHint(string toolId)
{
if (toolId == ToolSelectionRules.WEB_SEARCH_TOOL_ID)
return this.T("Enabling this tool also enables Read Web Page.");
if (this.IsSelectionLockedByDependency(toolId))
return this.T("This tool is currently required because Web Search is enabled.");
return null;
}
private async Task OpenSettings(string toolId)
{
var parameters = new DialogParameters<ToolSettingsDialog>

View File

@ -21,6 +21,7 @@
}
<SettingsPanelApp AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
<SettingsPanelTools />
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))

View File

@ -171,6 +171,8 @@ internal sealed class Program
builder.Services.AddSingleton<SettingsManager>();
builder.Services.AddSingleton<ToolSettingsService>();
builder.Services.AddSingleton<IToolImplementation, GetCurrentWeatherTool>();
builder.Services.AddSingleton<IToolImplementation, ReadWebPageTool>();
builder.Services.AddSingleton<IToolImplementation, SearXNGWebSearchTool>();
builder.Services.AddSingleton<ToolRegistry>();
builder.Services.AddSingleton<ToolExecutor>();
builder.Services.AddSingleton<ThreadSafeRandom>();

View File

@ -5,6 +5,7 @@ using System.Text.Json;
using AIStudio.Provider;
using AIStudio.Settings.DataModel;
using AIStudio.Tools;
using AIStudio.Tools.ToolCallingSystem;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
@ -349,7 +350,7 @@ public sealed class SettingsManager
{
var key = component.ToString();
if (this.ConfigurationData.Tools.DefaultToolIdsByComponent.TryGetValue(key, out var toolIds))
return [..toolIds];
return ToolSelectionRules.NormalizeSelection(toolIds);
return [];
}

View File

@ -1,4 +1,5 @@
using System.Net;
using System.Net.Http;
using System.Text;
using HtmlAgilityPack;
@ -23,10 +24,8 @@ public sealed class HTMLParser
/// <returns>The web content as text.</returns>
public async Task<string> LoadWebContentText(Uri url)
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var parser = new HtmlWeb();
var doc = await parser.LoadFromWebAsync(url, Encoding.UTF8, new NetworkCredential(), cts.Token);
return doc.ParsedText;
var response = await this.LoadWebPageAsync(url);
return response.Document.ParsedText;
}
/// <summary>
@ -36,14 +35,42 @@ public sealed class HTMLParser
/// <returns>The web content as an HTML string.</returns>
public async Task<string> LoadWebContentHTML(Uri url)
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var parser = new HtmlWeb();
var doc = await parser.LoadFromWebAsync(url, Encoding.UTF8, new NetworkCredential(), cts.Token);
var innerHtml = doc.DocumentNode.InnerHtml;
var response = await this.LoadWebPageAsync(url);
var innerHtml = response.Document.DocumentNode.InnerHtml;
return innerHtml;
}
public async Task<HTMLParserWebPage> LoadWebPageAsync(Uri url, CancellationToken token = default, int timeoutSeconds = 30)
{
using var httpClient = new HttpClient
{
Timeout = Timeout.InfiniteTimeSpan,
};
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(token);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
using var response = await httpClient.GetAsync(url, timeoutCts.Token);
response.EnsureSuccessStatusCode();
var html = await response.Content.ReadAsStringAsync(token);
var document = new HtmlDocument();
document.LoadHtml(html);
return new HTMLParserWebPage
{
RequestedUrl = url,
FinalUrl = response.RequestMessage?.RequestUri ?? url,
ContentType = response.Content.Headers.ContentType?.MediaType ?? string.Empty,
Document = document,
};
}
public string ExtractTitle(HtmlDocument document)
{
var title = document.DocumentNode.SelectSingleNode("//title")?.InnerText?.Trim();
return WebUtility.HtmlDecode(title ?? string.Empty).Trim();
}
/// <summary>
/// Converts HTML content to the Markdown format.
/// </summary>
@ -54,4 +81,15 @@ public sealed class HTMLParser
var markdownConverter = new Converter(MARKDOWN_PARSER_CONFIG);
return markdownConverter.Convert(html);
}
}
}
public sealed class HTMLParserWebPage
{
public required Uri RequestedUrl { get; init; }
public required Uri FinalUrl { get; init; }
public required string ContentType { get; init; }
public required HtmlDocument Document { get; init; }
}

View File

@ -22,6 +22,11 @@ public interface IToolImplementation
public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
this.T(fieldDefinition.Description);
public Task<ToolConfigurationState?> ValidateConfigurationAsync(
ToolDefinition definition,
IReadOnlyDictionary<string, string> settingsValues,
CancellationToken token = default) => Task.FromResult<ToolConfigurationState?>(null);
public Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default);
public string FormatTraceResult(string rawResult) => rawResult;

View File

@ -0,0 +1,215 @@
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Nodes;
using AIStudio.Tools;
using AIStudio.Tools.PluginSystem;
using HtmlAgilityPack;
namespace AIStudio.Tools.ToolCallingSystem;
public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation
{
private const int DEFAULT_TIMEOUT_SECONDS = 30;
private const int DEFAULT_MAX_CONTENT_CHARACTERS = 12000;
private const int MAX_TRACE_LENGTH = 4000;
private static readonly string[] REMOVED_NODE_XPATHS =
[
"//script",
"//style",
"//noscript",
"//nav",
"//footer",
"//aside",
"//form",
"//iframe",
"//*[@role='navigation']",
"//*[@role='contentinfo']",
"//*[@role='complementary']"
];
public string ImplementationKey => ToolSelectionRules.READ_WEB_PAGE_TOOL_ID;
public string Icon => Icons.Material.Filled.Article;
public IReadOnlySet<string> SensitiveTraceArgumentNames => new HashSet<string>(StringComparer.Ordinal);
public string GetDisplayName() => I18N.I.T("Read Web Page", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool));
public string GetDescription() => I18N.I.T("Load a single web page, extract its main HTML content, and return Markdown plus metadata for the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool));
public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
{
"timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)),
"maxContentCharacters" => I18N.I.T("Maximum Content Characters", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)),
_ => I18N.I.T(fieldDefinition.Title, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)),
};
public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
{
"timeoutSeconds" => I18N.I.T("Optional HTTP timeout for loading a web page in seconds.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)),
"maxContentCharacters" => I18N.I.T("Optional global truncation limit for extracted Markdown returned to the model.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)),
_ => I18N.I.T(fieldDefinition.Description, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)),
};
public Task<ToolConfigurationState?> ValidateConfigurationAsync(
ToolDefinition definition,
IReadOnlyDictionary<string, string> settingsValues,
CancellationToken token = default)
{
if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError))
{
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
{
IsConfigured = false,
Message = timeoutError,
});
}
if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError))
{
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
{
IsConfigured = false,
Message = contentError,
});
}
return Task.FromResult<ToolConfigurationState?>(null);
}
public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default)
{
var urlText = ReadRequiredString(arguments, "url");
if (!Uri.TryCreate(urlText, UriKind.Absolute, out var url) || url is not { Scheme: "http" or "https" })
throw new ArgumentException("Argument 'url' must be a valid HTTP or HTTPS URL.");
var timeoutSeconds = ReadOptionalPositiveIntSetting(context.SettingsValues, "timeoutSeconds") ?? DEFAULT_TIMEOUT_SECONDS;
var maxContentCharacters = ReadOptionalPositiveIntSetting(context.SettingsValues, "maxContentCharacters") ?? DEFAULT_MAX_CONTENT_CHARACTERS;
HTMLParserWebPage page;
try
{
page = await htmlParser.LoadWebPageAsync(url, token, timeoutSeconds);
}
catch (OperationCanceledException) when (!token.IsCancellationRequested)
{
throw new TimeoutException($"Loading the web page timed out after {timeoutSeconds} seconds.");
}
catch (HttpRequestException exception)
{
throw new InvalidOperationException($"Loading the web page failed: {exception.Message}", exception);
}
if (!IsSupportedHtmlContentType(page.ContentType))
throw new InvalidOperationException($"Unsupported content type '{page.ContentType}'. Only HTML pages are supported.");
var document = page.Document;
var title = htmlParser.ExtractTitle(document);
var contentRoot = document.DocumentNode.SelectSingleNode("//main") ??
document.DocumentNode.SelectSingleNode("//article") ??
document.DocumentNode.SelectSingleNode("//body") ??
document.DocumentNode;
RemoveNoiseNodes(contentRoot);
var markdown = htmlParser.ParseToMarkdown(contentRoot.InnerHtml).Trim();
var warnings = new JsonArray();
if (string.IsNullOrWhiteSpace(title))
warnings.Add("No title could be extracted from the page.");
if (string.IsNullOrWhiteSpace(markdown))
warnings.Add("The extracted page content is empty.");
else if (markdown.Length < 200)
warnings.Add("The extracted page content is very short and may be incomplete.");
if (markdown.Length > maxContentCharacters)
{
markdown = markdown[..maxContentCharacters].TrimEnd();
warnings.Add($"The extracted page content was truncated to {maxContentCharacters} characters.");
}
return new ToolExecutionResult
{
JsonContent = new JsonObject
{
["url"] = page.RequestedUrl.ToString(),
["final_url"] = page.FinalUrl.ToString(),
["title"] = title,
["markdown"] = markdown,
["warnings"] = warnings,
},
};
}
public string FormatTraceResult(string rawResult)
{
if (rawResult.Length <= MAX_TRACE_LENGTH)
return rawResult;
return $"{rawResult[..MAX_TRACE_LENGTH]}...";
}
private static void RemoveNoiseNodes(HtmlNode rootNode)
{
foreach (var xpath in REMOVED_NODE_XPATHS)
{
var nodes = rootNode.SelectNodes(xpath);
if (nodes is null)
continue;
foreach (var node in nodes.ToList())
node.Remove();
}
}
private static bool IsSupportedHtmlContentType(string? contentType) =>
string.IsNullOrWhiteSpace(contentType) ||
contentType.StartsWith("text/html", StringComparison.OrdinalIgnoreCase) ||
contentType.StartsWith("application/xhtml+xml", StringComparison.OrdinalIgnoreCase);
private static string ReadRequiredString(JsonElement arguments, string propertyName)
{
if (!arguments.TryGetProperty(propertyName, out var value) || value.ValueKind is not JsonValueKind.String)
throw new ArgumentException($"Missing required argument '{propertyName}'.");
var text = value.GetString()?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException($"Missing required argument '{propertyName}'.");
return text;
}
private static int? ReadOptionalPositiveIntSetting(IReadOnlyDictionary<string, string> settingsValues, string key)
{
if (!settingsValues.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
return null;
return int.TryParse(value, out var parsedValue) && parsedValue > 0 ? parsedValue : null;
}
private static bool TryReadOptionalPositiveInt(
IReadOnlyDictionary<string, string> settingsValues,
string key,
out int? value,
out string error)
{
value = null;
error = string.Empty;
if (!settingsValues.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
return true;
if (int.TryParse(rawValue, out var parsedValue) && parsedValue > 0)
{
value = parsedValue;
return true;
}
error = I18N.I.T($"The setting '{key}' must be a positive integer.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool));
return false;
}
}

View File

@ -0,0 +1,419 @@
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.ToolCallingSystem;
public sealed class SearXNGWebSearchTool : IToolImplementation
{
private const int DEFAULT_MAX_RESULTS = 5;
private const int DEFAULT_TIMEOUT_SECONDS = 20;
private const int MAX_TRACE_LENGTH = 4000;
public string ImplementationKey => "web_search";
public string Icon => Icons.Material.Filled.Language;
public IReadOnlySet<string> SensitiveTraceArgumentNames => new HashSet<string>(StringComparer.Ordinal);
public string GetDisplayName() => I18N.I.T("Web Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool));
public string GetDescription() => I18N.I.T("Search the web with a configured SearXNG instance and return structured JSON results for the model. When deeper content is needed, use Read Web Page on relevant result URLs.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool));
public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
{
"baseUrl" => I18N.I.T("SearXNG URL", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultLanguage" => I18N.I.T("Default Language", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultSafeSearch" => I18N.I.T("Default Safe Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultCategories" => I18N.I.T("Default Categories", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultEngines" => I18N.I.T("Default Engines", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"maxResults" => I18N.I.T("Maximum Results", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"timeoutSeconds" => I18N.I.T("Timeout Seconds", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
_ => I18N.I.T(fieldDefinition.Title, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
};
public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch
{
"baseUrl" => I18N.I.T("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultLanguage" => I18N.I.T("Optional fallback language code when the model does not provide a language.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultSafeSearch" => I18N.I.T("Optional safe search policy sent to SearXNG when configured.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultCategories" => I18N.I.T("Optional comma-separated default categories. Do not set this together with default engines.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"defaultEngines" => I18N.I.T("Optional comma-separated default engines. Do not set this together with default categories.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"maxResults" => I18N.I.T("Optional default maximum number of results returned to the model when the model does not provide a limit.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
"timeoutSeconds" => I18N.I.T("Optional HTTP timeout for the search request in seconds.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
_ => I18N.I.T(fieldDefinition.Description, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
};
public Task<ToolConfigurationState?> ValidateConfigurationAsync(
ToolDefinition definition,
IReadOnlyDictionary<string, string> settingsValues,
CancellationToken token = default)
{
settingsValues.TryGetValue("baseUrl", out var baseUrl);
var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out _, out var uriError);
if (!isValidBaseUrl)
{
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
{
IsConfigured = false,
Message = uriError,
});
}
var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories"));
var hasDefaultEngines = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultEngines"));
if (hasDefaultCategories && hasDefaultEngines)
{
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
{
IsConfigured = false,
Message = I18N.I.T("Default categories and default engines cannot both be set for the web search tool.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)),
});
}
if (!TryReadOptionalPositiveInt(settingsValues, "maxResults", out _, out var maxResultsError))
{
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
{
IsConfigured = false,
Message = maxResultsError,
});
}
if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError))
{
return Task.FromResult<ToolConfigurationState?>(new ToolConfigurationState
{
IsConfigured = false,
Message = timeoutError,
});
}
return Task.FromResult<ToolConfigurationState?>(null);
}
public async Task<ToolExecutionResult> ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default)
{
context.SettingsValues.TryGetValue("baseUrl", out var baseUrl);
var isValidBaseUrl = TryNormalizeSearchUri(baseUrl ?? string.Empty, out var searchUri, out var uriError);
if (!isValidBaseUrl)
throw new InvalidOperationException(uriError);
var query = ReadRequiredString(arguments, "query");
var categories = ReadOptionalStringArray(arguments, "categories");
var engines = ReadOptionalStringArray(arguments, "engines");
var language = ReadOptionalString(arguments, "language");
var timeRange = ReadOptionalString(arguments, "time_range");
var page = ReadOptionalPositiveInt(arguments, "page");
var requestedLimit = ReadOptionalPositiveInt(arguments, "limit");
if (timeRange is not null && timeRange is not ("day" or "month" or "year"))
throw new ArgumentException($"Invalid time_range '{timeRange}'.");
language = string.IsNullOrWhiteSpace(language) ? context.SettingsValues.GetValueOrDefault("defaultLanguage") : language;
var safeSearch = context.SettingsValues.GetValueOrDefault("defaultSafeSearch");
if (categories.Count == 0)
categories = SplitCommaSeparatedValues(context.SettingsValues.GetValueOrDefault("defaultCategories"));
if (engines.Count == 0)
engines = SplitCommaSeparatedValues(context.SettingsValues.GetValueOrDefault("defaultEngines"));
if (categories.Count > 0 && engines.Count > 0 && !string.IsNullOrWhiteSpace(context.SettingsValues.GetValueOrDefault("defaultCategories")) && !string.IsNullOrWhiteSpace(context.SettingsValues.GetValueOrDefault("defaultEngines")))
throw new InvalidOperationException(I18N.I.T("Default categories and default engines cannot both be set for the web search tool.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)));
var defaultLimit = ReadOptionalPositiveIntSetting(context.SettingsValues, "maxResults") ?? DEFAULT_MAX_RESULTS;
var effectiveLimit = requestedLimit ?? defaultLimit;
var timeoutSeconds = ReadOptionalPositiveIntSetting(context.SettingsValues, "timeoutSeconds") ?? DEFAULT_TIMEOUT_SECONDS;
var queryParameters = new List<KeyValuePair<string, string>>
{
new("q", query),
new("format", "json"),
};
if (categories.Count > 0)
queryParameters.Add(new KeyValuePair<string, string>("categories", string.Join(",", categories)));
if (engines.Count > 0)
queryParameters.Add(new KeyValuePair<string, string>("engines", string.Join(",", engines)));
if (!string.IsNullOrWhiteSpace(language))
queryParameters.Add(new KeyValuePair<string, string>("language", language));
if (!string.IsNullOrWhiteSpace(timeRange))
queryParameters.Add(new KeyValuePair<string, string>("time_range", timeRange));
if (page is not null)
queryParameters.Add(new KeyValuePair<string, string>("pageno", page.Value.ToString()));
if (!string.IsNullOrWhiteSpace(safeSearch))
queryParameters.Add(new KeyValuePair<string, string>("safesearch", safeSearch));
using var httpClient = new HttpClient
{
Timeout = Timeout.InfiniteTimeSpan,
};
using var request = new HttpRequestMessage(HttpMethod.Get, BuildRequestUri(searchUri, queryParameters));
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(token);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
using var response = await SendAsync(httpClient, request, timeoutCts.Token, timeoutSeconds, token);
var responseBody = await response.Content.ReadAsStringAsync(token);
if (!response.IsSuccessStatusCode)
{
var responseDetails = string.IsNullOrWhiteSpace(responseBody) ? string.Empty : $" Response body: {responseBody[..Math.Min(responseBody.Length, 400)]}";
throw new InvalidOperationException($"The SearXNG request failed with status code {(int)response.StatusCode} ({response.StatusCode}).{responseDetails}");
}
JsonNode? responseJson;
try
{
responseJson = JsonNode.Parse(responseBody);
}
catch (JsonException exception)
{
throw new InvalidOperationException($"The SearXNG response was not valid JSON: {exception.Message}", exception);
}
if (responseJson is not JsonObject responseObject)
throw new InvalidOperationException("The SearXNG response JSON must be an object.");
var resultArray = responseObject["results"] as JsonArray;
if (resultArray is not null && resultArray.Count > effectiveLimit)
{
var truncatedResults = new JsonArray();
foreach (var result in resultArray.Take(effectiveLimit))
truncatedResults.Add(result?.DeepClone());
responseObject["results"] = truncatedResults;
}
var requestJson = new JsonObject
{
["query"] = query,
["format"] = "json",
["limit"] = effectiveLimit,
};
if (categories.Count > 0)
requestJson["categories"] = BuildJsonArray(categories);
if (engines.Count > 0)
requestJson["engines"] = BuildJsonArray(engines);
if (!string.IsNullOrWhiteSpace(language))
requestJson["language"] = language;
if (!string.IsNullOrWhiteSpace(timeRange))
requestJson["time_range"] = timeRange;
if (page is not null)
requestJson["page"] = page.Value;
if (!string.IsNullOrWhiteSpace(safeSearch))
requestJson["safesearch"] = safeSearch;
return new ToolExecutionResult
{
JsonContent = new JsonObject
{
["request"] = requestJson,
["response"] = responseObject,
},
};
}
public string FormatTraceResult(string rawResult)
{
if (rawResult.Length <= MAX_TRACE_LENGTH)
return rawResult;
return $"{rawResult[..MAX_TRACE_LENGTH]}...";
}
private static string ReadRequiredString(JsonElement arguments, string propertyName)
{
var value = ReadOptionalString(arguments, propertyName);
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException($"Missing required argument '{propertyName}'.");
return value;
}
private static string? ReadOptionalString(JsonElement arguments, string propertyName)
{
if (!arguments.TryGetProperty(propertyName, out var value))
return null;
return value.ValueKind switch
{
JsonValueKind.Null => null,
JsonValueKind.String => value.GetString()?.Trim(),
_ => throw new ArgumentException($"Argument '{propertyName}' must be a string."),
};
}
private static int? ReadOptionalPositiveInt(JsonElement arguments, string propertyName)
{
if (!arguments.TryGetProperty(propertyName, out var value))
return null;
if (value.ValueKind is JsonValueKind.Null)
return null;
if (value.ValueKind is not JsonValueKind.Number || !value.TryGetInt32(out var intValue) || intValue <= 0)
throw new ArgumentException($"Argument '{propertyName}' must be a positive integer.");
return intValue;
}
private static List<string> ReadOptionalStringArray(JsonElement arguments, string propertyName)
{
if (!arguments.TryGetProperty(propertyName, out var value) || value.ValueKind is JsonValueKind.Null)
return [];
if (value.ValueKind is not JsonValueKind.Array)
throw new ArgumentException($"Argument '{propertyName}' must be an array of strings.");
var values = new List<string>();
foreach (var element in value.EnumerateArray())
{
if (element.ValueKind is not JsonValueKind.String)
throw new ArgumentException($"Argument '{propertyName}' must be an array of strings.");
var item = element.GetString()?.Trim();
if (!string.IsNullOrWhiteSpace(item))
values.Add(item);
}
return values;
}
private static JsonArray BuildJsonArray(IEnumerable<string> values)
{
var array = new JsonArray();
foreach (var value in values)
array.Add(value);
return array;
}
private static List<string> SplitCommaSeparatedValues(string? value) => value?
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct(StringComparer.Ordinal)
.ToList() ?? [];
private static int? ReadOptionalPositiveIntSetting(IReadOnlyDictionary<string, string> settingsValues, string key)
{
if (!settingsValues.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
return null;
return int.TryParse(value, out var parsedValue) && parsedValue > 0 ? parsedValue : null;
}
private static bool TryReadOptionalPositiveInt(
IReadOnlyDictionary<string, string> settingsValues,
string key,
out int? value,
out string error)
{
value = null;
error = string.Empty;
if (!settingsValues.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
return true;
if (int.TryParse(rawValue, out var parsedValue) && parsedValue > 0)
{
value = parsedValue;
return true;
}
error = I18N.I.T($"The setting '{key}' must be a positive integer.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool));
return false;
}
private static bool TryNormalizeSearchUri(string rawUrl, out Uri searchUri, out string error)
{
searchUri = null!;
error = string.Empty;
if (string.IsNullOrWhiteSpace(rawUrl))
{
error = I18N.I.T("A SearXNG URL is required.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool));
return false;
}
if (!Uri.TryCreate(rawUrl.Trim(), UriKind.Absolute, out var parsedUri))
{
error = I18N.I.T("The configured SearXNG URL is not a valid absolute URL.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool));
return false;
}
if (parsedUri.Scheme is not ("http" or "https"))
{
error = I18N.I.T("The configured SearXNG URL must start with http:// or https://.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool));
return false;
}
var basePath = parsedUri.AbsolutePath.TrimEnd('/');
if (basePath.EndsWith("/search", StringComparison.OrdinalIgnoreCase))
basePath = basePath[..^"/search".Length];
var normalizedPath = $"{basePath}/search";
var builder = new UriBuilder(parsedUri)
{
Path = normalizedPath,
Query = string.Empty,
Fragment = string.Empty,
};
searchUri = builder.Uri;
return true;
}
private static Uri BuildRequestUri(Uri searchUri, IEnumerable<KeyValuePair<string, string>> queryParameters)
{
var builder = new StringBuilder();
foreach (var parameter in queryParameters)
{
if (builder.Length > 0)
builder.Append('&');
builder.Append(WebUtility.UrlEncode(parameter.Key));
builder.Append('=');
builder.Append(WebUtility.UrlEncode(parameter.Value));
}
var uriBuilder = new UriBuilder(searchUri)
{
Query = builder.ToString(),
};
return uriBuilder.Uri;
}
private static async Task<HttpResponseMessage> SendAsync(
HttpClient httpClient,
HttpRequestMessage request,
CancellationToken requestToken,
int timeoutSeconds,
CancellationToken callerToken)
{
try
{
return await httpClient.SendAsync(request, requestToken);
}
catch (OperationCanceledException) when (!callerToken.IsCancellationRequested)
{
throw new TimeoutException($"The SearXNG request timed out after {timeoutSeconds} seconds.");
}
catch (Exception exception)
{
throw new InvalidOperationException($"The SearXNG request failed: {exception.Message}", exception);
}
}
}

View File

@ -79,6 +79,8 @@ public sealed class ToolConfigurationState
public bool IsConfigured { get; init; }
public List<string> MissingRequiredFields { get; init; } = [];
public string Message { get; init; } = string.Empty;
}
public sealed class ToolCatalogItem

View File

@ -100,7 +100,7 @@ public sealed class ToolRegistry
{
Definition = definition,
Implementation = implementation,
ConfigurationState = await this.toolSettingsService.GetConfigurationStateAsync(definition),
ConfigurationState = await this.toolSettingsService.GetConfigurationStateAsync(definition, implementation),
});
}
@ -119,7 +119,7 @@ public sealed class ToolRegistry
if (!modelCapabilities.Contains(Capability.CHAT_COMPLETION_API) || !modelCapabilities.Contains(Capability.FUNCTION_CALLING))
return [];
var selectedToolIdSet = selectedToolIds.ToHashSet(StringComparer.Ordinal);
var selectedToolIdSet = ToolSelectionRules.NormalizeSelection(selectedToolIds);
var definitions = this.GetDefinitionsForComponent(component).Where(x => selectedToolIdSet.Contains(x.Id)).ToList();
var result = new List<(ToolDefinition, IToolImplementation)>(definitions.Count);
foreach (var definition in definitions)
@ -127,7 +127,7 @@ public sealed class ToolRegistry
if (!this.implementationsByKey.TryGetValue(definition.ImplementationKey, out var implementation))
continue;
var configurationState = await this.toolSettingsService.GetConfigurationStateAsync(definition);
var configurationState = await this.toolSettingsService.GetConfigurationStateAsync(definition, implementation);
if (!configurationState.IsConfigured)
continue;

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
namespace AIStudio.Tools.ToolCallingSystem;
public static class ToolSelectionRules
{
public const string WEB_SEARCH_TOOL_ID = "web_search";
public const string READ_WEB_PAGE_TOOL_ID = "read_web_page";
public static HashSet<string> NormalizeSelection(IEnumerable<string> selectedToolIds)
{
var normalized = selectedToolIds.ToHashSet(StringComparer.Ordinal);
if (normalized.Contains(WEB_SEARCH_TOOL_ID))
normalized.Add(READ_WEB_PAGE_TOOL_ID);
return normalized;
}
public static bool IsRequiredBySelectedTools(string toolId, IEnumerable<string> selectedToolIds)
{
var normalized = NormalizeSelection(selectedToolIds);
return toolId == READ_WEB_PAGE_TOOL_ID && normalized.Contains(WEB_SEARCH_TOOL_ID);
}
}

View File

@ -29,7 +29,10 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer
return values;
}
public async Task<ToolConfigurationState> GetConfigurationStateAsync(ToolDefinition definition)
public async Task<ToolConfigurationState> GetConfigurationStateAsync(
ToolDefinition definition,
IToolImplementation? implementation = null,
CancellationToken token = default)
{
var values = await this.GetSettingsAsync(definition);
var missing = new List<string>();
@ -39,10 +42,25 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer
missing.Add(requiredField);
}
if (missing.Count > 0)
{
return new ToolConfigurationState
{
IsConfigured = false,
MissingRequiredFields = missing,
};
}
if (implementation is not null)
{
var validationState = await implementation.ValidateConfigurationAsync(definition, values, token);
if (validationState is not null && !validationState.IsConfigured)
return validationState;
}
return new ToolConfigurationState
{
IsConfigured = missing.Count == 0,
MissingRequiredFields = missing,
IsConfigured = true,
};
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"id": "read_web_page",
"implementationKey": "read_web_page",
"visibleIn": {
"chat": true,
"assistants": true
},
"settingsSchema": {
"type": "object",
"properties": {
"timeoutSeconds": {
"type": "string",
"secret": false
},
"maxContentCharacters": {
"type": "string",
"secret": false
}
},
"required": []
},
"function": {
"name": "read_web_page",
"description": "Load a single HTTP or HTTPS web page, extract its main content as Markdown, and return structured JSON metadata for the model.",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The full HTTP or HTTPS URL of the web page to read."
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
}

View File

@ -0,0 +1,103 @@
{
"schemaVersion": 1,
"id": "web_search",
"implementationKey": "web_search",
"visibleIn": {
"chat": true,
"assistants": true
},
"settingsSchema": {
"type": "object",
"properties": {
"baseUrl": {
"type": "string",
"secret": false
},
"defaultLanguage": {
"type": "string",
"secret": false
},
"defaultSafeSearch": {
"type": "string",
"enum": [
"0",
"1",
"2"
],
"secret": false
},
"defaultCategories": {
"type": "string",
"secret": false
},
"defaultEngines": {
"type": "string",
"secret": false
},
"maxResults": {
"type": "string",
"secret": false
},
"timeoutSeconds": {
"type": "string",
"secret": false
}
},
"required": [
"baseUrl"
]
},
"function": {
"name": "web_search",
"description": "Search the web via a configured SearXNG instance. Prefer categories for broad search intent. Use engines only when the user explicitly asks for specific search engines. Returns JSON with request metadata and the SearXNG response. When deeper content is needed, use read_web_page on relevant URLs from response.results.",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to send to SearXNG."
},
"categories": {
"type": "array",
"description": "Optional list of SearXNG categories to use for the search.",
"items": {
"type": "string"
}
},
"engines": {
"type": "array",
"description": "Optional list of specific SearXNG engines to use when the user requests them explicitly.",
"items": {
"type": "string"
}
},
"language": {
"type": "string",
"description": "Optional language code for the search."
},
"time_range": {
"type": "string",
"description": "Optional time range filter for engines that support it.",
"enum": [
"day",
"month",
"year"
]
},
"page": {
"type": "integer",
"description": "Optional search result page number starting at 1."
},
"limit": {
"type": "integer",
"description": "Optional maximum number of results to return to the model after local truncation."
}
},
"required": [
"query"
],
"additionalProperties": false
}
}
}