From 3c07f9f949af9828e01ab310d9857afa6c2839e0 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Mon, 13 Apr 2026 17:25:22 +0200 Subject: [PATCH 01/17] pr test --- app/MindWork AI Studio/Provider/BaseProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 2488db63..f4b36f00 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -1077,3 +1077,4 @@ public abstract class BaseProvider : IProvider, ISecretId _ => string.Empty, }; } + From d7bf542c1e0c291e69d55beead0b3c28ae33bd7a Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 12 May 2026 17:20:24 +0200 Subject: [PATCH 02/17] fixed translation bug where I18N.I.T was used instead of TB --- .../Assistants/I18N/allTexts.lua | 136 ++++---- .../plugin.lua | 296 ++++++++++++++--- .../plugin.lua | 314 +++++++++++++++--- .../GetCurrentWeatherTool.cs | 14 +- .../ReadWebPageTool.cs | 18 +- .../SearXNGWebSearchTool.cs | 50 +-- 6 files changed, 633 insertions(+), 195 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 8bb52092..0f63c32d 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -2638,6 +2638,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579 -- Name UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name" +-- No minimum confidence level chosen +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2828607242"] = "No minimum confidence level chosen" + +-- Minimum provider confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimum provider confidence" + -- Tool Settings UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Tool Settings" @@ -2764,6 +2770,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Close" -- No tools are available in this context. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "No tools are available in this context." +-- This tool requires provider confidence {0}. The selected provider has {1}. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "This tool requires provider confidence {0}. The selected provider has {1}." + -- Tool Selection UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Tool Selection" @@ -5920,6 +5929,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to -- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'" +-- The tool calling request failed with status code {0}. The provider message is: '{1}' +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "The tool calling request failed with status code {0}. The provider message is: '{1}'" + -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'" @@ -6853,98 +6865,98 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T35170 -- Tool description UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description" --- 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 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::TOOLCALLINGIMPLEMENTATIONS::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 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" +-- Current Weather +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T2280588182"] = "Current Weather" --- 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 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::TOOLCALLINGIMPLEMENTATIONS::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 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T459484184"] = "Demo Label" --- 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 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::TOOLCALLINGIMPLEMENTATIONS::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 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T626664936"] = "Required demo setting for validating tool settings in tests. It does not affect the weather result." --- 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 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::TOOLCALLINGIMPLEMENTATIONS::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 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T653336059"] = "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." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task 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 structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user." --- 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 structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user.", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1310829237"] = "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 structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user.\", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url" +-- Optional global truncation limit for extracted Markdown returned to the model. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optional global truncation limit for extracted Markdown returned to the model." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url" +-- Maximum Content Characters +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters" --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url" +-- Optional HTTP timeout for loading a web page in seconds. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optional HTTP timeout for loading a web page in seconds." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url" +-- Timeout Seconds +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Timeout Seconds" --- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user.", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "maxContentCharacters", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, "url -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3614129091"] = "Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user.\", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary settingsValues, CancellationToken token = default) { if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"maxContentCharacters\", out _, out var contentError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = contentError, }); } return Task.FromResult(null); } public async Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) { var urlText = ReadRequiredString(arguments, \"url" +-- Read Web Page +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Read Web Page" --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Maximum Results +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results" --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(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. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1342681591"] = "Optional comma-separated default categories. Do not set this together with default engines." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Default Safe Search +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1343180281"] = "Default Safe Search" --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(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. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1739312423"] = "Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- A SearXNG URL is required. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1746583720"] = "A SearXNG URL is required." --- 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::TOOLCALLINGIMPLEMENTATIONS::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" +-- Default Engines +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1865580137"] = "Default Engines" --- 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> 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 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::TOOLCALLINGIMPLEMENTATIONS::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> 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 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 fallback language code when the model does not provide a language. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1868101906"] = "Optional fallback language code when the model does not provide a language." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Default Categories +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2053347010"] = "Default Categories" --- 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::TOOLCALLINGIMPLEMENTATIONS::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 Language +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2526826120"] = "Default Language" --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- The configured SearXNG URL is not a valid absolute URL. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3038368943"] = "The configured SearXNG URL is not a valid absolute URL." --- 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(new ToolConfigurationState { IsConfigured = false, Message = maxResultsError, }); } if (!TryReadOptionalPositiveInt(settingsValues, "timeoutSeconds", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } return Task.FromResult(null); } public async Task 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::TOOLCALLINGIMPLEMENTATIONS::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(new ToolConfigurationState { IsConfigured = false, Message = maxResultsError, }); } if (!TryReadOptionalPositiveInt(settingsValues, \"timeoutSeconds\", out _, out var timeoutError)) { return Task.FromResult(new ToolConfigurationState { IsConfigured = false, Message = timeoutError, }); } return Task.FromResult(null); } public async Task 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" +-- Optional HTTP timeout for the search request in seconds. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3078115445"] = "Optional HTTP timeout for the search request in seconds." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Timeout Seconds +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3567699845"] = "Timeout Seconds" --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Optional default maximum number of results returned to the model when the model does not provide a limit. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3603838271"] = "Optional default maximum number of results returned to the model when the model does not provide a limit." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Web Search +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3815068443"] = "Web Search" --- Web Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetDescription() => I18N.I.T("Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions.", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3158851812"] = "Web Search\", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); public string GetDescription() => I18N.I.T(\"Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions.\", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Optional safe search policy sent to SearXNG when configured. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3967748757"] = "Optional safe search policy sent to SearXNG when configured." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(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. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4009446158"] = "Default categories and default engines cannot both be set for the web search tool." --- 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::TOOLCALLINGIMPLEMENTATIONS::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" +-- Optional comma-separated default engines. Do not set this together with default categories. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4108908537"] = "Optional comma-separated default engines. Do not set this together with default categories." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- The setting '{0}' must be a positive integer. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4199432074"] = "The setting '{0}' must be a positive integer." --- Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions.", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4262764011"] = "Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions.\", 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T764865565"] = "Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- The configured SearXNG URL must start with http:// or https://. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T944878454"] = "The configured SearXNG URL must start with http:// or https://." --- 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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault("defaultCategories -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::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 ValidateConfigurationAsync( ToolDefinition definition, IReadOnlyDictionary 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(new ToolConfigurationState { IsConfigured = false, Message = uriError, }); } var hasDefaultCategories = !string.IsNullOrWhiteSpace(settingsValues.GetValueOrDefault(\"defaultCategories" +-- SearXNG URL +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG URL" -- Pandoc Installation UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation" diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index babb8933..2dfbb4ec 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -1686,21 +1686,42 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CHATROLEEXTENSIONS::T601166687"] = "KI" -- Edit Message UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Nachricht bearbeiten" +-- Result +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347088452"] = "Ergebnis" + -- Do you really want to remove this message? UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Möchten Sie diese Nachricht wirklich löschen?" -- Yes, remove the AI response and edit it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Ja, entferne die KI-Antwort und bearbeite sie." +-- Failed +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1434043348"] = "Fehlgeschlagen" + +-- Tool Calls ({0}) +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1493057571"] = "Tool-Aufrufe" + +-- Executed +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1564757972"] = "Ausgeführt" + -- Yes, regenerate it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Ja, neu generieren" +-- No result +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1684269223"] = "Kein Ergebnis" + -- Yes, remove it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Ja, entferne es" -- Number of sources UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Anzahl der Quellen" +-- Show {0} tool calls +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1981771421"] = "{0} Toolaufrufe anzeigen" + +-- Show tool call for {0} +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2004842583"] = "Tool-Aufruf für {0}" + -- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Möchten Sie diese Nachricht wirklich bearbeiten? Um die Nachricht zu bearbeiten, wird die Antwort der KI gelöscht." @@ -1710,6 +1731,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Entfern -- Regenerate Message UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Nachricht neu erstellen" +-- Arguments +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2738624831"] = "Argumente" + -- Number of attachments UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Anzahl der Anhänge" @@ -1719,9 +1743,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Der Inh -- Edit UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Bearbeiten" +-- Unknown +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3424652889"] = "Unbekannt" + -- Regenerate UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Neu generieren" +-- Blocked +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3816336467"] = "Blockiert" + -- Do you really want to regenerate this message? UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3878878761"] = "Möchten Sie diese Nachricht wirklich neu generieren?" @@ -1731,9 +1761,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Nachric -- No, keep it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, behalten" +-- No tool calls +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4224149521"] = "Verstanden." + -- Export Chat to Microsoft Word UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in Microsoft Word exportieren" +-- No arguments +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T931993614"] = "Keine Argumente" + -- The local image file does not exist. Skipping the image. UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "Die lokale Bilddatei existiert nicht. Das Bild wird übersprungen." @@ -1884,15 +1920,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T252 -- Select a minimum confidence level UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2579793544"] = "Wählen Sie ein minimales Vertrauensniveau aus" --- You have selected 1 preview feature. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T1384241824"] = "Sie haben 1 Vorschaufunktion ausgewählt." - --- No preview features selected. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] = "Keine Vorschaufunktionen ausgewählt." - --- You have selected {0} preview features. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "Sie haben {0} Vorschaufunktionen ausgewählt." - -- Preselected provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Vorausgewählter Anbieter" @@ -2592,6 +2619,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237 -- Export configuration UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Konfiguration exportieren" +-- Settings +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Einstellungen" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1725856265"] = "Beschreibung" + +-- Icon +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728"] = "Symbol" + +-- Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T176751696"] = "Globale Einstellungen für jedes Tool konfigurieren. Standardwerte für Tools für Chats und Assistenten werden in den entsprechenden Funktionseinstellungen konfiguriert." + +-- This tool still needs to be configured. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "Dieses Werkzeug muss noch konfiguriert werden." + +-- Missing required settings: {0} +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Fehlende erforderliche Einstellungen: {0}" + +-- Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name" + +-- No minimum confidence level chosen +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2828607242"] = "Kein Mindestvertrauensniveau ausgewählt" + +-- Minimum provider confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimale Anbieterzuverlässigkeit" + +-- Tool Settings +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Werkzeugeinstellungen" + +-- State +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T502047894"] = "Status" + -- No transcription provider configured yet. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "Es ist bisher kein Anbieter für Transkriptionen konfiguriert." @@ -2661,6 +2721,66 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1392042694"] = "Rep -- License: UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1908172666"] = "Lizenz:" +-- Tool selection is hidden +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2096103917"] = "Werkzeugauswahl ist ausgeblendet" + +-- You have selected 1 tool. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2493128368"] = "Sie haben 1 Werkzeug ausgewählt." + +-- Choose which tools should be preselected for new runs of this assistant. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2696618758"] = "Wählen Sie aus, welche Werkzeuge für neue Ausführungen dieses Assistenten standardmäßig vorausgewählt sein sollen." + +-- Default tools for this assistant +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3253667950"] = "Standardwerkzeuge für diesen Assistenten" + +-- Tool selection is visible +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3384582069"] = "Die Werkzeugauswahl ist sichtbar" + +-- Show tool selection in this assistant? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3494508870"] = "Werkzeugauswahl in diesem Assistenten anzeigen?" + +-- You have selected {0} tools. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3729156356"] = "Sie haben {0} Werkzeuge ausgewählt." + +-- No tools selected. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3934845540"] = "Keine Tools ausgewählt." + +-- Default tools for chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T907403808"] = "Standardwerkzeuge für den Chat" + +-- Choose which tools should be preselected for new chats. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T948842182"] = "Wählen Sie aus, welche Werkzeuge für neue Chats vorausgewählt sein sollen." + +-- This tool is currently required because Web Search is enabled. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1351725609"] = "Dieses Werkzeug ist derzeit erforderlich, da die Websuche aktiviert ist." + +-- 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"] = "Werkzeugänderungen sind gesperrt, während eine Antwort ausgeführt wird. Ihre aktuelle Auswahl wird unten angezeigt und gilt nach Abschluss der Ausführung ab der nächsten Nachricht wieder." + +-- Enabling this tool also enables Read Web Page. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3023833839"] = "Das Aktivieren dieses Werkzeugs aktiviert auch „Webseite lesen“." + +-- This tool requires provider confidence {0}. The selected provider has {1}. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "Dieses Werkzeug erfordert Anbieter-Vertrauen {0}. Der ausgewählte Anbieter hat {1}." + +-- Required settings are missing. Configure this tool before enabling it. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3119156561"] = "Erforderliche Einstellungen fehlen. Konfigurieren Sie dieses Tool, bevor Sie es aktivieren." + +-- The selected provider or model does not support tool calling. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3364063757"] = "Der ausgewählte Anbieter oder das ausgewählte Modell unterstützt keine Tool-Aufrufe." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Schließen" + +-- No tools are available in this context. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "Keine Werkzeuge sind in diesem Kontext verfügbar." + +-- Tool Selection +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Werkzeugauswahl" + +-- Select tools +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T998515990"] = "Tools auswählen" + -- You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1015366320"] = "Sie werden mit den KI-Systemen über ihre Stimme interagieren. Dafür möchten wir Spracheingabe (Sprache-zu-Text) und Sprachausgabe (Text-zu-Sprache) integrieren. Später soll außerdem ein natürlicher Gesprächsfluss möglich sein, also eine nahtlose Unterhaltung." @@ -4698,6 +4818,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T13933 -- Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1528169602"] = "Wählen Sie Aspekte vorab aus, auf die sich das LLM bei der Erstellung von Folien konzentrieren soll, z. B. Aufzählungspunkte oder bestimmte Themen, die hervorgehoben werden sollen." +-- Slide Planner Assistant options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1549358578"] = "Die Optionen des Folienplaner-Assistenten sind vorausgewählt." + +-- No Slide Planner Assistant options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1694374279"] = "Es sind keine Optionen für den Slide-Planer-Assistenten vorausgewählt." + -- Choose whether the assistant should use the app default profile, no profile, or a specific profile. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1766361623"] = "Wählen Sie aus, ob der Assistent das Standardprofil der App, kein Profil oder ein bestimmtes Profil verwenden soll." @@ -4707,9 +4833,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T20146 -- Which audience organizational level should be preselected? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T216511105"] = "Welche organisatorische Ebene der Zielgruppe soll vorausgewählt werden?" --- Preselect Slide Planner Assistant options? -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T227645894"] = "Optionen des Folienplaner-Assistenten vorauswählen?" - -- Preselect a profile UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2322771068"] = "Profil vorauswählen" @@ -4726,26 +4849,23 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T25714 UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2645589441"] = "Altersgruppe der Zielgruppe vorauswählen" -- Assistant: Slide Planner Assistant Options -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3215549988"] = "Assistent: Optionen für die Erstellung von Folien" +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3226042276"] = "Assistent: Optionen für den Folienplanungs-Assistenten" -- Which audience expertise should be preselected? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3228597992"] = "Welche Expertise der Zielgruppe sollte vorausgewählt werden?" +-- Preselect Slide Planner Assistant options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T339924858"] = "Optionen des Assistenten „Folienplaner“ vorab auswählen?" + -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3448155331"] = "Schließen" -- Preselect important aspects UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3705987833"] = "Wichtige Aspekte vorauswählen" --- No Slide Planner Assistant options are preselected -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T4214398691"] = "Keine Optionen für den Folienplaner-Assistenten sind vorausgewählt." - -- Preselect the audience profile UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T861397972"] = "Zielgruppenprofil vorauswählen" --- Slide Planner Assistant options are preselected -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T93124146"] = "Optionen des Folienplaner-Assistenten sind vorausgewählt" - -- Which audience age group should be preselected? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T956845877"] = "Welche Altersgruppe der Zielgruppe sollte vorausgewählt sein?" @@ -5004,6 +5124,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3547 -- Preselect e-mail options? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832719342"] = "E-Mail-Optionen vorauswählen?" +-- Save +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T1294818664"] = "Speichern" + +-- Tool Settings +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3730473128"] = "Werkzeugeinstellungen" + +-- The selected tool could not be loaded. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"] = "Das ausgewählte Werkzeug konnte nicht geladen werden." + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Abbrechen" + -- Save UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Speichern" @@ -5799,6 +5931,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "Wir haben ve -- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Möglicherweise können Sie diesen Anbieter von Ihrem Standort aus nicht nutzen. Die Nachricht des Anbieters lautet: „{2}“." +-- The tool calling request failed with status code {0}. The provider message is: '{1}' +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "Die Tool-Aufrufanfrage ist mit dem Statuscode {0} fehlgeschlagen. Die Meldung des Anbieters lautet: „{1}“" + -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Etwas wurde nicht gefunden. Die Nachricht des Anbieters lautet: „{2}“" @@ -5835,30 +5970,6 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3788466789"] = "Der Anbieter b -- The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T991875725"] = "Der Anbieter betreibt seinen Dienst von China aus. Im Verdachtsfall können Behörden in den jeweiligen Ländern auf ihre Daten zugreifen. **Ihre Daten werden jedoch nicht zum Trainieren** verwendet." --- Medium -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T163471254"] = "Mittel" - --- Moderate -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T177463328"] = "Mäßig" - --- Unknown confidence level -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T1811522309"] = "Unbekanntes Vertrauensniveau" - --- No provider selected -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2897045472"] = "Kein Anbieter ausgewählt" - --- Low -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2984088865"] = "Niedrig" - --- Untrusted -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3063224793"] = "Nicht vertrauenswürdig" - --- High -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3188327965"] = "Hoch" - --- Very Low -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T786675843"] = "Sehr niedrig" - -- Self-hosted UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T146444217"] = "Selbst gehostet" @@ -6726,6 +6837,105 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4174900468"] = "Von den Dat -- Sources provided by the AI UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4261248356"] = "Von der KI bereitgestellte Quellen" +-- Tool +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Werkzeug" + +-- Tool description +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Werkzeugbeschreibung" + +-- Current Weather +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T2280588182"] = "Aktuelles Wetter" + +-- Demo Label +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T459484184"] = "Demo-Beschriftung" + +-- Required demo setting for validating tool settings in tests. It does not affect the weather result. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T626664936"] = "Erforderliche Demo-Einstellung zur Validierung der Werkzeug-Einstellungen in Tests. Sie beeinflusst das Wetterergebnis nicht." + +-- 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T653336059"] = "Verwenden Sie dieses Demo-Tool, um das aktuelle Wetter für eine bestimmte Stadt und ein bestimmtes Bundesland abzurufen. Es dient in erster Linie dazu, Tool-Aufrufe und Tool-Einstellungen in AI Studio zu demonstrieren." + +-- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Klar — ich kann eine einzelne Webseite laden, ihren Hauptinhalt extrahieren und daraus eine nutzbare, strukturierte Grundlage erstellen. Dem Nutzer gebe ich dann **eine natürlich formulierte Antwort**, nicht den rohen Extraktions-Output. Bitte sende mir einfach **die URL** der Seite, die ich verarbeiten soll. Wenn du möchtest, kannst du auch kurz dazuschreiben, **was genau ich daraus beantworten oder zusammenfassen soll**." + +-- Optional global truncation limit for extracted Markdown returned to the model. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optionales globales Kürzungslimit für extrahiertes Markdown, das an das Modell zurückgegeben wird." + +-- Maximum Content Characters +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximale Inhaltszeichen" + +-- Optional HTTP timeout for loading a web page in seconds. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optionales HTTP-Zeitlimit zum Laden einer Webseite in Sekunden." + +-- Timeout Seconds +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Zeitlimit in Sekunden" + +-- Read Web Page +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Webseite lesen" + +-- Maximum Results +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximale Anzahl an Ergebnissen" + +-- Optional comma-separated default categories. Do not set this together with default engines. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1342681591"] = "Optionale, durch Kommas getrennte Standardkategorien. Nicht zusammen mit Standard-Engines festlegen." + +-- Default Safe Search +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1343180281"] = "Standard-SafeSearch" + +-- Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1739312423"] = "Basis-URL der SearXNG-Instanz. Sie können entweder die Stamm-URL der Instanz oder den Endpunkt /search eingeben." + +-- A SearXNG URL is required. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1746583720"] = "Eine SearXNG-URL ist erforderlich." + +-- Default Engines +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1865580137"] = "Standard-Engines" + +-- Optional fallback language code when the model does not provide a language. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1868101906"] = "Optionaler Fallback-Sprachcode, wenn das Modell keine Sprache angibt." + +-- Default Categories +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2053347010"] = "Standardkategorien" + +-- Default Language +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2526826120"] = "Standardsprache" + +-- The configured SearXNG URL is not a valid absolute URL. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3038368943"] = "Die konfigurierte SearXNG-URL ist keine gültige absolute URL." + +-- Optional HTTP timeout for the search request in seconds. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3078115445"] = "Optionales HTTP-Timeout für die Suchanfrage in Sekunden." + +-- Timeout Seconds +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3567699845"] = "Zeitüberschreitung in Sekunden" + +-- Optional default maximum number of results returned to the model when the model does not provide a limit. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3603838271"] = "Optionale Standardhöchstzahl der an das Modell zurückgegebenen Ergebnisse, wenn das Modell kein Limit angibt." + +-- Web Search +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3815068443"] = "Websuche" + +-- Optional safe search policy sent to SearXNG when configured. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3967748757"] = "Optionale SafeSearch-Richtlinie, die bei entsprechender Konfiguration an SearXNG gesendet wird." + +-- Default categories and default engines cannot both be set for the web search tool. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4009446158"] = "Standardkategorien und Standard-Engines können für das Websuch-Tool nicht gleichzeitig festgelegt werden." + +-- Optional comma-separated default engines. Do not set this together with default categories. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4108908537"] = "Optionale, durch Kommas getrennte Standard-Engines. Nicht zusammen mit Standardkategorien festlegen." + +-- The setting '{0}' must be a positive integer. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4199432074"] = "Die Einstellung „{0}“ muss eine positive ganze Zahl sein." + +-- Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T764865565"] = "Suche im Web mit einer konfigurierten SearXNG-Instanz und gib Kandidaten-URLs für das Modell zurück. Verwende „Webseite lesen“ für relevante Ergebnis-URLs, bevor du sachliche oder detaillierte Webfragen beantwortest." + +-- The configured SearXNG URL must start with http:// or https://. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T944878454"] = "Die konfigurierte SearXNG-URL muss mit http:// oder https:// beginnen." + +-- SearXNG URL +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG-URL" + -- Pandoc Installation UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc-Installation" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 7a310d2d..9c780ab2 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -1458,9 +1458,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T1793579367 -- Text content UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T1820253043"] = "Text content" --- Slide Planner Assistant -UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T1883918574"] = "Slide Planner Assistant" - -- Please provide a text or at least one valid document or image. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T2013746884"] = "Please provide a text or at least one valid document or image." @@ -1491,6 +1488,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T2823798965 -- This assistant helps you create clear, structured slides from long texts or documents. Enter a presentation title and provide the content either as text or with one or more documents. Important aspects allow you to add instructions to the LLM regarding output or formatting. Set the number of slides either directly or based on your desired presentation duration. You can also specify the number of bullet points. If the default value of 0 is not changed, the LLM will independently determine how many slides or bullet points to generate. The output can be flexibly generated in various languages and tailored to a specific audience. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T2910177051"] = "This assistant helps you create clear, structured slides from long texts or documents. Enter a presentation title and provide the content either as text or with one or more documents. Important aspects allow you to add instructions to the LLM regarding output or formatting. Set the number of slides either directly or based on your desired presentation duration. You can also specify the number of bullet points. If the default value of 0 is not changed, the LLM will independently determine how many slides or bullet points to generate. The output can be flexibly generated in various languages and tailored to a specific audience." +-- Slide Planner Assistant +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T2924755246"] = "Slide Planner Assistant" + -- The result of your previous slide builder session. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SLIDEBUILDER::SLIDEASSISTANT::T3000286990"] = "The result of your previous slide builder session." @@ -1686,21 +1686,42 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CHATROLEEXTENSIONS::T601166687"] = "AI" -- Edit Message UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Edit Message" +-- Result +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347088452"] = "Result" + -- Do you really want to remove this message? UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you really want to remove this message?" -- Yes, remove the AI response and edit it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it" +-- Failed +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1434043348"] = "Failed" + +-- Tool Calls ({0}) +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1493057571"] = "Tool Calls ({0})" + +-- Executed +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1564757972"] = "Executed" + -- Yes, regenerate it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it" +-- No result +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1684269223"] = "No result" + -- Yes, remove it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it" -- Number of sources UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Number of sources" +-- Show {0} tool calls +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1981771421"] = "Show {0} tool calls" + +-- Show tool call for {0} +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2004842583"] = "Show tool call for {0}" + -- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted." @@ -1710,6 +1731,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes -- Regenerate Message UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message" +-- Arguments +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2738624831"] = "Arguments" + -- Number of attachments UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments" @@ -1719,9 +1743,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot -- Edit UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit" +-- Unknown +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3424652889"] = "Unknown" + -- Regenerate UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate" +-- Blocked +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3816336467"] = "Blocked" + -- Do you really want to regenerate this message? UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3878878761"] = "Do you really want to regenerate this message?" @@ -1731,9 +1761,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove -- No, keep it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it" +-- No tool calls +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4224149521"] = "No tool calls" + -- Export Chat to Microsoft Word UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word" +-- No arguments +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T931993614"] = "No arguments" + -- The local image file does not exist. Skipping the image. UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "The local image file does not exist. Skipping the image." @@ -1884,15 +1920,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T252 -- Select a minimum confidence level UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2579793544"] = "Select a minimum confidence level" --- You have selected 1 preview feature. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T1384241824"] = "You have selected 1 preview feature." - --- No preview features selected. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] = "No preview features selected." - --- You have selected {0} preview features. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features." - -- Preselected provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider" @@ -2592,6 +2619,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237 -- Export configuration UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Export configuration" +-- Settings +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Settings" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1725856265"] = "Description" + +-- Icon +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728"] = "Icon" + +-- Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T176751696"] = "Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings." + +-- This tool still needs to be configured. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "This tool still needs to be configured." + +-- 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" + +-- No minimum confidence level chosen +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2828607242"] = "No minimum confidence level chosen" + +-- Minimum provider confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimum provider confidence" + +-- Tool Settings +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Tool Settings" + +-- State +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T502047894"] = "State" + -- No transcription provider configured yet. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet." @@ -2661,6 +2721,66 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1392042694"] = "Ope -- License: UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1908172666"] = "License:" +-- Tool selection is hidden +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2096103917"] = "Tool selection is hidden" + +-- You have selected 1 tool. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2493128368"] = "You have selected 1 tool." + +-- Choose which tools should be preselected for new runs of this assistant. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2696618758"] = "Choose which tools should be preselected for new runs of this assistant." + +-- Default tools for this assistant +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3253667950"] = "Default tools for this assistant" + +-- Tool selection is visible +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3384582069"] = "Tool selection is visible" + +-- Show tool selection in this assistant? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3494508870"] = "Show tool selection in this assistant?" + +-- You have selected {0} tools. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3729156356"] = "You have selected {0} tools." + +-- No tools selected. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3934845540"] = "No tools selected." + +-- Default tools for chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T907403808"] = "Default tools for chat" + +-- 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." + +-- This tool requires provider confidence {0}. The selected provider has {1}. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "This tool requires provider confidence {0}. The selected provider has {1}." + +-- 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." + +-- The selected provider or model does not support tool calling. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3364063757"] = "The selected provider or model does not support tool calling." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Close" + +-- No tools are available in this context. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "No tools are available in this context." + +-- Tool Selection +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Tool Selection" + +-- Select tools +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T998515990"] = "Select tools" + -- You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1015366320"] = "You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation." @@ -4698,6 +4818,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T13933 -- Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1528169602"] = "Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize." +-- Slide Planner Assistant options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1549358578"] = "Slide Planner Assistant options are preselected" + +-- No Slide Planner Assistant options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1694374279"] = "No Slide Planner Assistant options are preselected" + -- Choose whether the assistant should use the app default profile, no profile, or a specific profile. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1766361623"] = "Choose whether the assistant should use the app default profile, no profile, or a specific profile." @@ -4707,9 +4833,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T20146 -- Which audience organizational level should be preselected? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T216511105"] = "Which audience organizational level should be preselected?" --- Preselect Slide Planner Assistant options? -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T227645894"] = "Preselect Slide Planner Assistant options?" - -- Preselect a profile UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2322771068"] = "Preselect a profile" @@ -4726,26 +4849,23 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T25714 UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2645589441"] = "Preselect the audience age group" -- Assistant: Slide Planner Assistant Options -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3215549988"] = "Assistant: Slide Planner Assistant Options" +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3226042276"] = "Assistant: Slide Planner Assistant Options" -- Which audience expertise should be preselected? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3228597992"] = "Which audience expertise should be preselected?" +-- Preselect Slide Planner Assistant options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T339924858"] = "Preselect Slide Planner Assistant options?" + -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3448155331"] = "Close" -- Preselect important aspects UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3705987833"] = "Preselect important aspects" --- No Slide Planner Assistant options are preselected -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T4214398691"] = "No Slide Planner Assistant options are preselected" - -- Preselect the audience profile UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T861397972"] = "Preselect the audience profile" --- Slide Planner Assistant options are preselected -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T93124146"] = "Slide Planner Assistant options are preselected" - -- Which audience age group should be preselected? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T956845877"] = "Which audience age group should be preselected?" @@ -5004,6 +5124,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3547 -- Preselect e-mail options? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832719342"] = "Preselect e-mail options?" +-- Save +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T1294818664"] = "Save" + +-- Tool Settings +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3730473128"] = "Tool Settings" + +-- The selected tool could not be loaded. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"] = "The selected tool could not be loaded." + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Cancel" + -- Save UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Save" @@ -5190,9 +5322,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1617786407"] = "Coding" -- Analyze a text or an email for tasks you need to complete. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1728590051"] = "Analyze a text or an email for tasks you need to complete." --- Slide Planner Assistant -UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1883918574"] = "Slide Planner Assistant" - -- Text Summarizer UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1907192403"] = "Text Summarizer" @@ -5226,6 +5355,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2830810750"] = "AI Studio Develop -- Generate a job posting for a given job description. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2831103254"] = "Generate a job posting for a given job description." +-- Slide Planner Assistant +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2924755246"] = "Slide Planner Assistant" + -- My Tasks UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3011450657"] = "My Tasks" @@ -5799,6 +5931,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to -- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'" +-- The tool calling request failed with status code {0}. The provider message is: '{1}' +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "The tool calling request failed with status code {0}. The provider message is: '{1}'" + -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'" @@ -5835,30 +5970,6 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3788466789"] = "The provider o -- The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T991875725"] = "The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes." --- Medium -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T163471254"] = "Medium" - --- Moderate -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T177463328"] = "Moderate" - --- Unknown confidence level -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T1811522309"] = "Unknown confidence level" - --- No provider selected -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2897045472"] = "No provider selected" - --- Low -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2984088865"] = "Low" - --- Untrusted -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3063224793"] = "Untrusted" - --- High -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3188327965"] = "High" - --- Very Low -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T786675843"] = "Very Low" - -- Self-hosted UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T146444217"] = "Self-hosted" @@ -6171,9 +6282,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T1546040625"] = "My Task -- Grammar & Spelling Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T166453786"] = "Grammar & Spelling Assistant" --- Slide Planner Assistant -UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T1883918574"] = "Slide Planner Assistant" - -- Legal Check Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T1886447798"] = "Legal Check Assistant" @@ -6189,6 +6297,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2684676843"] = "Text Su -- Synonym Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2921123194"] = "Synonym Assistant" +-- Slide Planner Assistant +UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2924755246"] = "Slide Planner Assistant" + -- Document Analysis Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Document Analysis Assistant" @@ -6726,6 +6837,105 @@ 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" +-- Tool +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Tool" + +-- Tool description +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description" + +-- Current Weather +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T2280588182"] = "Current Weather" + +-- Demo Label +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T459484184"] = "Demo Label" + +-- Required demo setting for validating tool settings in tests. It does not affect the weather result. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T626664936"] = "Required demo setting for validating tool settings in tests. It does not affect the weather result." + +-- 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T653336059"] = "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." + +-- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user." + +-- Optional global truncation limit for extracted Markdown returned to the model. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optional global truncation limit for extracted Markdown returned to the model." + +-- Maximum Content Characters +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters" + +-- Optional HTTP timeout for loading a web page in seconds. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optional HTTP timeout for loading a web page in seconds." + +-- Timeout Seconds +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Timeout Seconds" + +-- Read Web Page +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Read Web Page" + +-- Maximum Results +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results" + +-- Optional comma-separated default categories. Do not set this together with default engines. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1342681591"] = "Optional comma-separated default categories. Do not set this together with default engines." + +-- Default Safe Search +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1343180281"] = "Default Safe Search" + +-- Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1739312423"] = "Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint." + +-- A SearXNG URL is required. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1746583720"] = "A SearXNG URL is required." + +-- Default Engines +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1865580137"] = "Default Engines" + +-- Optional fallback language code when the model does not provide a language. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1868101906"] = "Optional fallback language code when the model does not provide a language." + +-- Default Categories +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2053347010"] = "Default Categories" + +-- Default Language +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2526826120"] = "Default Language" + +-- The configured SearXNG URL is not a valid absolute URL. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3038368943"] = "The configured SearXNG URL is not a valid absolute URL." + +-- Optional HTTP timeout for the search request in seconds. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3078115445"] = "Optional HTTP timeout for the search request in seconds." + +-- Timeout Seconds +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3567699845"] = "Timeout Seconds" + +-- Optional default maximum number of results returned to the model when the model does not provide a limit. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3603838271"] = "Optional default maximum number of results returned to the model when the model does not provide a limit." + +-- Web Search +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3815068443"] = "Web Search" + +-- Optional safe search policy sent to SearXNG when configured. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3967748757"] = "Optional safe search policy sent to SearXNG when configured." + +-- Default categories and default engines cannot both be set for the web search tool. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4009446158"] = "Default categories and default engines cannot both be set for the web search tool." + +-- Optional comma-separated default engines. Do not set this together with default categories. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4108908537"] = "Optional comma-separated default engines. Do not set this together with default categories." + +-- The setting '{0}' must be a positive integer. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4199432074"] = "The setting '{0}' must be a positive integer." + +-- Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T764865565"] = "Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions." + +-- The configured SearXNG URL must start with http:// or https://. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T944878454"] = "The configured SearXNG URL must start with http:// or https://." + +-- SearXNG URL +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG URL" + -- Pandoc Installation UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation" diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs index a767346e..23bce8f3 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs @@ -5,26 +5,28 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations; public sealed class GetCurrentWeatherTool : IToolImplementation { + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); + public string ImplementationKey => "get_current_weather"; public string Icon => Icons.Material.Filled.Cloud; public IReadOnlySet SensitiveTraceArgumentNames => new HashSet(StringComparer.Ordinal); - public string GetDisplayName() => I18N.I.T("Current Weather", typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); + public string GetDisplayName() => TB("Current Weather"); - 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 GetDescription() => TB("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."); 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)), + "demoLabel" => TB("Demo Label"), + _ => TB(fieldDefinition.Title), }; 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)), + "demoLabel" => TB("Required demo setting for validating tool settings in tests. It does not affect the weather result."), + _ => TB(fieldDefinition.Description), }; public Task ExecuteAsync(JsonElement arguments, ToolExecutionContext context, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs index 581cb8a6..4a9c997f 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs @@ -7,6 +7,8 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations; public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation { + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); + private const int DEFAULT_TIMEOUT_SECONDS = 30; private const int DEFAULT_MAX_CONTENT_CHARACTERS = 12000; private const int MAX_TRACE_LENGTH = 12000; @@ -32,22 +34,22 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation public IReadOnlySet SensitiveTraceArgumentNames => new HashSet(StringComparer.Ordinal); - public string GetDisplayName() => I18N.I.T("Read Web Page", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); + public string GetDisplayName() => TB("Read Web Page"); - public string GetDescription() => I18N.I.T("Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); + public string GetDescription() => TB("Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user."); 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)), + "timeoutSeconds" => TB("Timeout Seconds"), + "maxContentCharacters" => TB("Maximum Content Characters"), + _ => TB(fieldDefinition.Title), }; 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)), + "timeoutSeconds" => TB("Optional HTTP timeout for loading a web page in seconds."), + "maxContentCharacters" => TB("Optional global truncation limit for extracted Markdown returned to the model."), + _ => TB(fieldDefinition.Description), }; public Task ValidateConfigurationAsync( diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/SearXNGWebSearchTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/SearXNGWebSearchTool.cs index c3a13b90..8dfde6e8 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/SearXNGWebSearchTool.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/SearXNGWebSearchTool.cs @@ -8,6 +8,8 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations; public sealed class SearXNGWebSearchTool : IToolImplementation { + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); + private const int DEFAULT_MAX_RESULTS = 5; private const int DEFAULT_TIMEOUT_SECONDS = 20; private const int MAX_TRACE_LENGTH = 4000; @@ -18,32 +20,32 @@ public sealed class SearXNGWebSearchTool : IToolImplementation public IReadOnlySet SensitiveTraceArgumentNames => new HashSet(StringComparer.Ordinal); - public string GetDisplayName() => I18N.I.T("Web Search", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); + public string GetDisplayName() => TB("Web Search"); - public string GetDescription() => I18N.I.T("Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); + public string GetDescription() => TB("Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions."); 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)), + "baseUrl" => TB("SearXNG URL"), + "defaultLanguage" => TB("Default Language"), + "defaultSafeSearch" => TB("Default Safe Search"), + "defaultCategories" => TB("Default Categories"), + "defaultEngines" => TB("Default Engines"), + "maxResults" => TB("Maximum Results"), + "timeoutSeconds" => TB("Timeout Seconds"), + _ => TB(fieldDefinition.Title), }; 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)), + "baseUrl" => TB("Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint."), + "defaultLanguage" => TB("Optional fallback language code when the model does not provide a language."), + "defaultSafeSearch" => TB("Optional safe search policy sent to SearXNG when configured."), + "defaultCategories" => TB("Optional comma-separated default categories. Do not set this together with default engines."), + "defaultEngines" => TB("Optional comma-separated default engines. Do not set this together with default categories."), + "maxResults" => TB("Optional default maximum number of results returned to the model when the model does not provide a limit."), + "timeoutSeconds" => TB("Optional HTTP timeout for the search request in seconds."), + _ => TB(fieldDefinition.Description), }; public Task ValidateConfigurationAsync( @@ -69,7 +71,7 @@ public sealed class SearXNGWebSearchTool : IToolImplementation return Task.FromResult(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)), + Message = TB("Default categories and default engines cannot both be set for the web search tool."), }); } @@ -122,7 +124,7 @@ public sealed class SearXNGWebSearchTool : IToolImplementation 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))); + throw new InvalidOperationException(TB("Default categories and default engines cannot both be set for the web search tool.")); var defaultLimit = ReadOptionalPositiveIntSetting(context.SettingsValues, "maxResults") ?? DEFAULT_MAX_RESULTS; var effectiveLimit = requestedLimit ?? defaultLimit; @@ -425,7 +427,7 @@ public sealed class SearXNGWebSearchTool : IToolImplementation return true; } - error = I18N.I.T($"The setting '{key}' must be a positive integer.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); + error = string.Format(TB("The setting '{0}' must be a positive integer."), key); return false; } @@ -436,19 +438,19 @@ public sealed class SearXNGWebSearchTool : IToolImplementation if (string.IsNullOrWhiteSpace(rawUrl)) { - error = I18N.I.T("A SearXNG URL is required.", typeof(SearXNGWebSearchTool).Namespace, nameof(SearXNGWebSearchTool)); + error = TB("A SearXNG URL is required."); 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)); + error = TB("The configured SearXNG URL is not a valid absolute URL."); 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)); + error = TB("The configured SearXNG URL must start with http:// or https://."); return false; } From 7c5acf3b4544348fd3325b348f38f4500517d94e Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 12 May 2026 17:23:44 +0200 Subject: [PATCH 03/17] added possible null values to all web_search properties to match the optional parameter design. Otherwise the strict schema forbids empty parameters and return BadRequest --- .../wwwroot/tool_definitions/web_search.json | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/app/MindWork AI Studio/wwwroot/tool_definitions/web_search.json b/app/MindWork AI Studio/wwwroot/tool_definitions/web_search.json index e74e3246..775929d8 100644 --- a/app/MindWork AI Studio/wwwroot/tool_definitions/web_search.json +++ b/app/MindWork AI Studio/wwwroot/tool_definitions/web_search.json @@ -59,43 +59,68 @@ "description": "The search query." }, "categories": { - "type": "array", + "type": [ + "array", + "null" + ], "description": "Optional list of SearXNG categories to use for the search.", "items": { "type": "string" } }, "engines": { - "type": "array", + "type": [ + "array", + "null" + ], "description": "Optional list of specific SearXNG engines to use when the user requests them explicitly.", "items": { "type": "string" } }, "language": { - "type": "string", + "type": [ + "string", + "null" + ], "description": "Optional language code for the search." }, "time_range": { - "type": "string", + "type": [ + "string", + "null" + ], "description": "Optional time range filter for engines that support it.", "enum": [ + null, "day", "month", "year" ] }, "page": { - "type": "integer", + "type": [ + "integer", + "null" + ], "description": "Optional search result page number starting at 1." }, "limit": { - "type": "integer", + "type": [ + "integer", + "null" + ], "description": "Optional maximum number of results to return to the model after local truncation." } }, "required": [ - "query" + "query", + "categories", + "engines", + "language", + "time_range", + "page", + "limit" ], "additionalProperties": false } From 4c9e9b7a9ce2877122e2b2d03f1ab1bc43b07a36 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 12 May 2026 17:26:02 +0200 Subject: [PATCH 04/17] adding the tools and the confidence level to the SettingsManager --- .../Plugins/configuration/plugin.lua | 10 ++++++++ .../Settings/DataModel/Data.cs | 4 ++-- .../Settings/DataModel/DataTools.cs | 15 +++++++++++- .../Settings/SettingsManager.cs | 24 +++++++++++++++++++ .../Tools/PluginSystem/PluginConfiguration.cs | 3 +++ .../PluginSystem/PluginFactory.Loading.cs | 4 ++++ 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 03a9b0f4..ffe1a019 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -212,6 +212,16 @@ CONFIG["SETTINGS"] = {} -- Examples are: "CmdOrControl+Shift+D", "Alt+F9", "F8" -- CONFIG["SETTINGS"]["DataApp.ShortcutVoiceRecording"] = "CmdOrControl+1" +-- Configure the minimum provider confidence level required for individual tools. +-- Tool IDs include: web_search, read_web_page, get_current_weather +-- Allowed values are: NONE, UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH +-- Defaults: web_search = MEDIUM, read_web_page = MEDIUM, get_current_weather = NONE +-- CONFIG["SETTINGS"]["DataTools.MinimumProviderConfidenceByToolId"] = { +-- ["web_search"] = "MEDIUM", +-- ["read_web_page"] = "MEDIUM", +-- ["get_current_weather"] = "NONE" +-- } + -- Example chat templates for this configuration: CONFIG["CHAT_TEMPLATES"] = {} diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs index 6affb20a..7dd8ce80 100644 --- a/app/MindWork AI Studio/Settings/DataModel/Data.cs +++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs @@ -137,5 +137,5 @@ public sealed class Data public DataI18N I18N { get; init; } = new(); - public DataTools Tools { get; init; } = new(); -} \ No newline at end of file + public DataTools Tools { get; init; } = new(x => x.Tools); +} diff --git a/app/MindWork AI Studio/Settings/DataModel/DataTools.cs b/app/MindWork AI Studio/Settings/DataModel/DataTools.cs index 773ada7e..1ace1059 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataTools.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataTools.cs @@ -1,10 +1,23 @@ +using System.Linq.Expressions; + +using AIStudio.Settings; + namespace AIStudio.Settings.DataModel; -public sealed class DataTools +public sealed class DataTools(Expression>? configSelection = null) { + public DataTools() : this(null) + { + } + public Dictionary> Settings { get; set; } = []; public Dictionary> DefaultToolIdsByComponent { get; set; } = []; public HashSet VisibleToolSelectionComponents { get; set; } = []; + + public Dictionary MinimumProviderConfidenceByToolId { get; set; } = ManagedConfiguration.Register>( + configSelection, + x => x.MinimumProviderConfidenceByToolId, + new Dictionary(StringComparer.Ordinal)); } diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 25678efe..f162d9c9 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -373,6 +373,30 @@ public sealed class SettingsManager this.ConfigurationData.Tools.VisibleToolSelectionComponents.Remove(key); } + public ConfidenceLevel GetMinimumProviderConfidenceForTool(string toolId) + { + if (this.ConfigurationData.Tools.MinimumProviderConfidenceByToolId.TryGetValue(toolId, out var configuredLevel) && + Enum.TryParse(configuredLevel, true, out var confidenceLevel) && + confidenceLevel is not ConfidenceLevel.UNKNOWN) + { + return confidenceLevel; + } + + return ToolSelectionRules.GetDefaultMinimumProviderConfidence(toolId); + } + + public void SetMinimumProviderConfidenceForTool(string toolId, ConfidenceLevel confidenceLevel) + { + var defaultLevel = ToolSelectionRules.GetDefaultMinimumProviderConfidence(toolId); + if (confidenceLevel == defaultLevel) + { + this.ConfigurationData.Tools.MinimumProviderConfidenceByToolId.Remove(toolId); + return; + } + + this.ConfigurationData.Tools.MinimumProviderConfidenceByToolId[toolId] = confidenceLevel.ToString(); + } + public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider) { if(llmProvider is LLMProviders.NONE) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 99031624..8d409c31 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -132,6 +132,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: global voice recording shortcut ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShortcutVoiceRecording, this.Id, settingsTable, dryRun); + + // Config: minimum provider confidence per tool + ManagedConfiguration.TryProcessConfiguration(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, this.Id, settingsTable, dryRun); // Handle configured LLM providers: PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index f110e766..e4f290b9 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -237,6 +237,10 @@ public static partial class PluginFactory // Check for the voice recording shortcut: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.ShortcutVoiceRecording, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + + // Check for minimum provider confidence per tool: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; if (wasConfigurationChanged) { From 0cd24c5ec1e9fb6e11155b224a38074c21bd3435 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 12 May 2026 17:45:58 +0200 Subject: [PATCH 05/17] added logging and ui context for bad requests of tool callings --- .../Assistants/I18N/allTexts.lua | 6 +++--- .../plugin.lua | 3 +++ .../plugin.lua | 3 +++ app/MindWork AI Studio/Provider/BaseProvider.cs | 15 ++++++++++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 0f63c32d..fbf478f4 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -5929,15 +5929,15 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to -- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'" --- The tool calling request failed with status code {0}. The provider message is: '{1}' -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "The tool calling request failed with status code {0}. The provider message is: '{1}'" - -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'" -- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'." +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details." + -- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'" diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 2dfbb4ec..f1430425 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -5934,6 +5934,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "Wir haben ve -- The tool calling request failed with status code {0}. The provider message is: '{1}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "Die Tool-Aufrufanfrage ist mit dem Statuscode {0} fehlgeschlagen. Die Meldung des Anbieters lautet: „{1}“" +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "Die Tool-Aufrufanfrage ist mit dem Statuscode {0} fehlgeschlagen. Details finden Sie in den Logs." + -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Etwas wurde nicht gefunden. Die Nachricht des Anbieters lautet: „{2}“" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 9c780ab2..28fe8a07 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -5934,6 +5934,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to -- The tool calling request failed with status code {0}. The provider message is: '{1}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "The tool calling request failed with status code {0}. The provider message is: '{1}'" +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details." + -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'" diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index f4b36f00..de8091a6 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -629,6 +629,7 @@ public abstract class BaseProvider : IProvider, ISecretId chatThread.RuntimeComponent, chatThread.RuntimeSelectedToolIds, this.Provider.GetModelCapabilities(chatModel), + this.Provider.GetConfidence(settingsManager).Level, settingsManager.IsToolSelectionVisible(chatThread.RuntimeComponent)); if (runnableTools.Count > 0) @@ -653,13 +654,19 @@ public abstract class BaseProvider : IProvider, ISecretId var response = await this.ExecuteChatCompletionRequest(requestDto, requestPath, requestedSecret, headersAction, token); var responseMessage = response?.Choices.FirstOrDefault()?.Message; if (responseMessage is null) + { + currentAssistantContent!.ToolRuntimeStatus = new(); + await currentAssistantContent.StreamingEvent(); yield break; + } if (responseMessage.ToolCalls.Count == 0) { currentAssistantContent!.ToolRuntimeStatus = new(); if (!string.IsNullOrWhiteSpace(responseMessage.Content)) yield return new ContentStreamChunk(responseMessage.Content, []); + else if (toolCallCount > 0) + yield return new ContentStreamChunk("The model completed the tool call but did not return a final answer.", []); yield break; } @@ -763,7 +770,14 @@ public abstract class BaseProvider : IProvider, ISecretId using var response = await this.httpClient.SendAsync(request, token); if (!response.IsSuccessStatusCode) + { + var responseBody = await response.Content.ReadAsStringAsync(token); + this.logger.LogError("Tool calling chat completion request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody); + await MessageBus.INSTANCE.SendError(new( + Icons.Material.Filled.Build, + string.Format(TB("The tool calling request failed with status code {0}. See the logs for details."), (int)response.StatusCode))); return null; + } return await response.Content.ReadFromJsonAsync(JSON_SERIALIZER_OPTIONS, token); } @@ -1077,4 +1091,3 @@ public abstract class BaseProvider : IProvider, ISecretId _ => string.Empty, }; } - From f2c63ac4b59d887c1e90184d81f3edae76aee6c3 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 12 May 2026 17:51:11 +0200 Subject: [PATCH 06/17] use individual confidence levels for each tool to protect tool callings from using unsafe providers --- .../Components/ChatComponent.razor.cs | 6 ++- .../Settings/SettingsPanelTools.razor | 12 ++++++ .../Settings/SettingsPanelTools.razor.cs | 41 ++++++++++++++++++- .../Components/ToolSelection.razor | 8 +++- .../Components/ToolSelection.razor.cs | 36 ++++++++++++++++ .../Provider/OpenAI/ProviderOpenAI.cs | 12 +++--- .../ToolCallingSystem/ToolExecutionModels.cs | 3 ++ .../Tools/ToolCallingSystem/ToolRegistry.cs | 10 +++++ .../ToolCallingSystem/ToolSelectionRules.cs | 12 ++++++ .../wwwroot/changelog/v26.3.1.md | 3 +- 10 files changed, 134 insertions(+), 9 deletions(-) diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 88e1ec20..45fabf86 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -81,7 +81,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable protected override async Task OnInitializedAsync() { // Apply the filters for the message bus: - this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED ]); + this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED, Event.CONFIGURATION_CHANGED ]); // Configure the spellchecking for the user input: this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); @@ -1007,6 +1007,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable case Event.WORKSPACE_LOADED_CHAT_CHANGED: await this.LoadedChatChanged(); break; + + case Event.CONFIGURATION_CHANGED: + await this.InvokeAsync(this.StateHasChanged); + break; } } diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor index c10c1983..238b0133 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor @@ -1,3 +1,4 @@ +@using AIStudio.Provider @using AIStudio.Tools.ToolCallingSystem @inherits SettingsPanelBase @@ -11,6 +12,7 @@ @T("Icon") @T("Name") @T("Description") + @T("Minimum provider confidence") @T("State") @T("Settings") @@ -24,6 +26,16 @@ @context.Implementation.GetDescription() + + + @foreach (var confidenceLevel in this.GetSelectableConfidenceLevels()) + { + + @this.GetConfidenceLevelName(confidenceLevel) + + } + + @if (context.ConfigurationState.IsConfigured) { diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs index c978d16c..36b0e7a8 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs @@ -1,4 +1,6 @@ +using AIStudio.Provider; using AIStudio.Dialogs.Settings; +using AIStudio.Settings; using AIStudio.Tools; using AIStudio.Tools.ToolCallingSystem; @@ -15,8 +17,9 @@ public partial class SettingsPanelTools : SettingsPanelBase protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions()); + await base.OnInitializedAsync(); } private async Task OpenSettings(string toolId) @@ -47,4 +50,40 @@ public partial class SettingsPanelTools : SettingsPanelBase return item.Implementation.GetSettingsFieldLabel(fieldName, fieldDefinition); } + + private IEnumerable GetSelectableConfidenceLevels() => + Enum.GetValues().OrderBy(x => x).Where(x => x is not ConfidenceLevel.UNKNOWN); + + private string GetCurrentConfidenceLevelName(ToolCatalogItem item) => this.GetConfidenceLevelName(this.GetMinimumProviderConfidence(item)); + + private string GetConfidenceLevelName(ConfidenceLevel confidenceLevel) => confidenceLevel is ConfidenceLevel.NONE + ? this.T("No minimum confidence level chosen") + : confidenceLevel.GetName(); + + private string SetCurrentConfidenceLevelColorStyle(ToolCatalogItem item) => + $"background-color: {this.GetMinimumProviderConfidence(item).GetColor(this.SettingsManager)};"; + + private bool IsToolConfidenceManaged() => + ManagedConfiguration.TryGet(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, out var meta) && meta.IsLocked; + + private ConfidenceLevel GetMinimumProviderConfidence(ToolCatalogItem item) => this.SettingsManager.GetMinimumProviderConfidenceForTool(item.Definition.Id); + + private async Task ChangeMinimumProviderConfidence(ToolCatalogItem item, ConfidenceLevel confidenceLevel) + { + this.SettingsManager.SetMinimumProviderConfidenceForTool(item.Definition.Id, confidenceLevel); + await this.SettingsManager.StoreSettings(); + this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions()); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.CONFIGURATION_CHANGED: + this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions()); + await this.InvokeAsync(this.StateHasChanged); + break; + } + } } diff --git a/app/MindWork AI Studio/Components/ToolSelection.razor b/app/MindWork AI Studio/Components/ToolSelection.razor index a98f02e2..23613c26 100644 --- a/app/MindWork AI Studio/Components/ToolSelection.razor +++ b/app/MindWork AI Studio/Components/ToolSelection.razor @@ -40,10 +40,12 @@ var isSelected = this.SelectedToolIds.Contains(item.Definition.Id); var isConfigured = item.ConfigurationState.IsConfigured; var dependencyHint = this.GetDependencyHint(item.Definition.Id); + var providerConfidenceHint = this.GetProviderConfidenceHint(item); + var isBlockedByProviderConfidence = this.IsBlockedByProviderConfidence(item); - + @item.Implementation.GetDisplayName() @@ -59,6 +61,10 @@ { @dependencyHint } + @if (!string.IsNullOrWhiteSpace(providerConfidenceHint)) + { + @providerConfidenceHint + } } } diff --git a/app/MindWork AI Studio/Components/ToolSelection.razor.cs b/app/MindWork AI Studio/Components/ToolSelection.razor.cs index 7f96fb06..0f81eb0c 100644 --- a/app/MindWork AI Studio/Components/ToolSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ToolSelection.razor.cs @@ -43,11 +43,21 @@ public partial class ToolSelection : MSGComponentBase base.OnParametersSet(); } + protected override async Task OnInitializedAsync() + { + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); + await base.OnInitializedAsync(); + } + private bool SupportsTools => this.LLMProvider != AIStudio.Settings.Provider.NONE && this.LLMProvider.GetModelCapabilities().Contains(Capability.CHAT_COMPLETION_API) && this.LLMProvider.GetModelCapabilities().Contains(Capability.FUNCTION_CALLING); + private ConfidenceLevel ProviderConfidence => this.LLMProvider == AIStudio.Settings.Provider.NONE + ? ConfidenceLevel.NONE + : this.LLMProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level; + private async Task ToggleSelection() { this.showSelection = !this.showSelection; @@ -72,6 +82,10 @@ public partial class ToolSelection : MSGComponentBase private bool IsSelectionLockedByDependency(string toolId) => ToolSelectionRules.IsRequiredBySelectedTools(toolId, this.SelectedToolIds); + private ConfidenceLevel GetMinimumProviderConfidence(ToolCatalogItem item) => this.SettingsManager.GetMinimumProviderConfidenceForTool(item.Definition.Id); + + private bool IsBlockedByProviderConfidence(ToolCatalogItem item) => !ToolSelectionRules.IsProviderConfidenceAllowed(this.ProviderConfidence, this.GetMinimumProviderConfidence(item)); + private string? GetDependencyHint(string toolId) { if (toolId == ToolSelectionRules.WEB_SEARCH_TOOL_ID) @@ -83,6 +97,17 @@ public partial class ToolSelection : MSGComponentBase return null; } + private string? GetProviderConfidenceHint(ToolCatalogItem item) + { + if (!this.IsBlockedByProviderConfidence(item)) + return null; + + return string.Format( + this.T("This tool requires provider confidence {0}. The selected provider has {1}."), + this.GetMinimumProviderConfidence(item).GetName(), + this.ProviderConfidence.GetName()); + } + private async Task OpenSettings(string toolId) { var parameters = new DialogParameters @@ -95,4 +120,15 @@ public partial class ToolSelection : MSGComponentBase this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component); this.StateHasChanged(); } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.CONFIGURATION_CHANGED when this.showSelection: + this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component); + await this.InvokeAsync(this.StateHasChanged); + break; + } + } } diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index 5f717d8b..3d7d280e 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -5,6 +5,7 @@ using System.Text.Json; using AIStudio.Chat; using AIStudio.Settings; +using AIStudio.Tools.ToolCallingSystem; namespace AIStudio.Provider.OpenAI; @@ -78,11 +79,12 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https // // Prepare the tools we want to use: // - IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) switch - { - true => [ ProviderTools.WEB_SEARCH ], - _ => [] - }; + var providerConfidence = this.Provider.GetConfidence(settingsManager).Level; + var minimumWebSearchConfidence = settingsManager.GetMinimumProviderConfidenceForTool(ToolSelectionRules.WEB_SEARCH_TOOL_ID); + var isWebSearchAllowed = ToolSelectionRules.IsProviderConfidenceAllowed(providerConfidence, minimumWebSearchConfidence); + IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) && isWebSearchAllowed + ? [ ProviderTools.WEB_SEARCH ] + : []; // Parse the API parameters: diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs index f90cf538..05f718c0 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; +using AIStudio.Provider; using AIStudio.Settings; namespace AIStudio.Tools.ToolCallingSystem; @@ -90,6 +91,8 @@ public sealed class ToolCatalogItem public required IToolImplementation Implementation { get; init; } public required ToolConfigurationState ConfigurationState { get; init; } + + public ConfidenceLevel MinimumProviderConfidence { get; init; } = ConfidenceLevel.NONE; } public sealed class ToolSelectionState diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs index ea4732d2..9b95162f 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs @@ -1,6 +1,7 @@ using System.Text.Json; using AIStudio.Provider; +using AIStudio.Settings; using Microsoft.AspNetCore.Hosting; @@ -9,6 +10,7 @@ namespace AIStudio.Tools.ToolCallingSystem; public sealed class ToolRegistry { private readonly ILogger logger; + private readonly SettingsManager settingsManager; private readonly ToolSettingsService toolSettingsService; private readonly Dictionary definitionsById = new(StringComparer.Ordinal); private readonly Dictionary implementationsByKey = new(StringComparer.Ordinal); @@ -16,10 +18,12 @@ public sealed class ToolRegistry public ToolRegistry( IWebHostEnvironment webHostEnvironment, IEnumerable implementations, + SettingsManager settingsManager, ToolSettingsService toolSettingsService, ILogger logger) { this.logger = logger; + this.settingsManager = settingsManager; this.toolSettingsService = toolSettingsService; foreach (var implementation in implementations) @@ -101,6 +105,7 @@ public sealed class ToolRegistry Definition = definition, Implementation = implementation, ConfigurationState = await this.toolSettingsService.GetConfigurationStateAsync(definition, implementation), + MinimumProviderConfidence = this.settingsManager.GetMinimumProviderConfidenceForTool(definition.Id), }); } @@ -111,6 +116,7 @@ public sealed class ToolRegistry AIStudio.Tools.Components component, IEnumerable selectedToolIds, IReadOnlyCollection modelCapabilities, + ConfidenceLevel providerConfidence, bool isToolSelectionVisible) { if (!isToolSelectionVisible) @@ -131,6 +137,10 @@ public sealed class ToolRegistry if (!configurationState.IsConfigured) continue; + var minimumToolConfidence = this.settingsManager.GetMinimumProviderConfidenceForTool(definition.Id); + if (!ToolSelectionRules.IsProviderConfidenceAllowed(providerConfidence, minimumToolConfidence)) + continue; + result.Add((definition, implementation)); } diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs index fc5b9d39..fcd34580 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; +using AIStudio.Provider; + namespace AIStudio.Tools.ToolCallingSystem; public static class ToolSelectionRules @@ -22,4 +24,14 @@ public static class ToolSelectionRules var normalized = NormalizeSelection(selectedToolIds); return toolId == READ_WEB_PAGE_TOOL_ID && normalized.Contains(WEB_SEARCH_TOOL_ID); } + + public static ConfidenceLevel GetDefaultMinimumProviderConfidence(string toolId) => toolId switch + { + WEB_SEARCH_TOOL_ID => ConfidenceLevel.MEDIUM, + READ_WEB_PAGE_TOOL_ID => ConfidenceLevel.MEDIUM, + _ => ConfidenceLevel.NONE, + }; + + public static bool IsProviderConfidenceAllowed(ConfidenceLevel providerConfidence, ConfidenceLevel minimumToolConfidence) => + minimumToolConfidence is ConfidenceLevel.NONE || providerConfidence >= minimumToolConfidence; } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index f4d5274d..ddf9a890 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -7,6 +7,7 @@ - Added a start-page setting, so AI Studio can now open directly on your preferred page when the app starts. Configuration plugins can also provide and optionally lock this default for organizations. - Added math rendering in chats for LaTeX display formulas, including block formats such as `$$ ... $$` and `\[ ... \]`. - Added the latest OpenAI models. +- Added minimum provider confidence settings for tools, so sensitive tools such as web search can be limited to trusted providers. Configuration plugins can also manage these defaults for organizations. - Released the document analysis assistant after an intense testing phase. - Improved enterprise deployment for organizations: administrators can now provide up to 10 centrally managed enterprise configuration slots, use policy files on Linux and macOS, and continue using older configuration formats as a fallback during migration. - Improved the profile selection for assistants and the chat. You can now explicitly choose between the app default profile, no profile, or a specific profile. @@ -27,4 +28,4 @@ - Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown. Thanks, Inga, for reporting this issue and providing some context on how to reproduce it. - Fixed security issues in the native app runtime by strengthening how AI Studio creates and protects the secret values used for its internal secure connection. - Updated several security-sensitive Rust dependencies in the native runtime to address known vulnerabilities. -- Updated .NET to v9.0.14 \ No newline at end of file +- Updated .NET to v9.0.14 From 1f42c8ad4ce28d6886c712c46faa1291b9932b91 Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 15:26:33 +0200 Subject: [PATCH 07/17] Adding allowed private hosts for read_web_page tool to managed settings. This enables a whitelist because all other private hosts will be blocked --- app/MindWork AI Studio/Plugins/configuration/plugin.lua | 8 ++++++++ app/MindWork AI Studio/Settings/DataModel/DataTools.cs | 5 +++++ .../Tools/PluginSystem/PluginConfiguration.cs | 3 +++ .../Tools/PluginSystem/PluginFactory.Loading.cs | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index ffe1a019..419e804f 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -222,6 +222,14 @@ CONFIG["SETTINGS"] = {} -- ["get_current_weather"] = "NONE" -- } +-- Configure private or VPN hosts that the Read Web Page tool may access. +-- Public web pages do not need to be listed here. +-- Private hosts listed here still require a provider with HIGH confidence before any page content is sent to the model. +-- Separate host patterns with commas. Wildcards only match subdomains, so add the root domain separately if needed. +-- Examples: +-- CONFIG["SETTINGS"]["DataTools.ReadWebPageAllowedPrivateHosts"] = "dlr.de, *.dlr.de" +-- CONFIG["SETTINGS"]["DataTools.ReadWebPageAllowedPrivateHosts.AllowUserOverride"] = false + -- Example chat templates for this configuration: CONFIG["CHAT_TEMPLATES"] = {} diff --git a/app/MindWork AI Studio/Settings/DataModel/DataTools.cs b/app/MindWork AI Studio/Settings/DataModel/DataTools.cs index 1ace1059..ef4f9220 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataTools.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataTools.cs @@ -20,4 +20,9 @@ public sealed class DataTools(Expression>? configSelection configSelection, x => x.MinimumProviderConfidenceByToolId, new Dictionary(StringComparer.Ordinal)); + + public string ReadWebPageAllowedPrivateHosts { get; set; } = ManagedConfiguration.Register( + configSelection, + x => x.ReadWebPageAllowedPrivateHosts, + string.Empty); } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 8d409c31..37de8f4f 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -135,6 +135,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: minimum provider confidence per tool ManagedConfiguration.TryProcessConfiguration(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, this.Id, settingsTable, dryRun); + + // Config: private hosts allowed for the read web page tool + ManagedConfiguration.TryProcessConfiguration(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, this.Id, settingsTable, dryRun); // Handle configured LLM providers: PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index e4f290b9..bf60f9b8 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -241,6 +241,10 @@ public static partial class PluginFactory // Check for minimum provider confidence per tool: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + + // Check for private hosts allowed for the read web page tool: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; if (wasConfigurationChanged) { From 6da97c7c8029fb6a5141efbd4c3b03c4cb53787e Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 15:28:57 +0200 Subject: [PATCH 08/17] adding the provider confidence to the tool context; introducing a ToolExecutionBlockedException --- .../Provider/BaseProvider.cs | 1 + .../ToolCallingSystem/ToolExecutionModels.cs | 4 +++ .../Tools/ToolCallingSystem/ToolExecutor.cs | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index de8091a6..7b44eb9c 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -713,6 +713,7 @@ public abstract class BaseProvider : IProvider, ISecretId toolCall.Function.Name, toolCall.Function.Arguments, runnableTools, + this.Provider.GetConfidence(settingsManager).Level, toolCallCount, token); diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs index 05f718c0..2472ec61 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs @@ -13,6 +13,8 @@ public sealed class ToolExecutionContext public required SettingsManager SettingsManager { get; init; } public required IReadOnlyDictionary SettingsValues { get; init; } + + public ConfidenceLevel ProviderConfidence { get; init; } = ConfidenceLevel.UNKNOWN; } public sealed class ToolExecutionResult @@ -30,6 +32,8 @@ public sealed class ToolExecutionResult } } +public sealed class ToolExecutionBlockedException(string message) : Exception(message); + public enum ToolInvocationTraceStatus { NONE = 0, diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs index 4c8dd2f0..1fe9e403 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs @@ -1,5 +1,7 @@ using System.Text.Json; +using AIStudio.Provider; + using Microsoft.Extensions.DependencyInjection; namespace AIStudio.Tools.ToolCallingSystem; @@ -11,6 +13,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService) string toolName, string argumentsJson, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools, + ConfidenceLevel providerConfidence, int order, CancellationToken token = default) { @@ -40,6 +43,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService) Definition = definition, SettingsManager = Program.SERVICE_PROVIDER.GetRequiredService(), SettingsValues = settingsValues, + ProviderConfidence = providerConfidence, }, token); return (result.ToModelContent(), new ToolInvocationTrace @@ -55,6 +59,31 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService) Result = implementation.FormatTraceResult(result.ToModelContent()), }); } + catch (ToolExecutionBlockedException exception) + { + Dictionary formattedArguments = []; + try + { + using var document = JsonDocument.Parse(string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson); + formattedArguments = FormatArguments(document.RootElement, implementation.SensitiveTraceArgumentNames); + } + catch + { + } + + return (exception.Message, new ToolInvocationTrace + { + Order = order, + ToolId = definition.Id, + ToolName = implementation.GetDisplayName(), + ToolIcon = implementation.Icon, + ToolCallId = toolCallId, + Status = ToolInvocationTraceStatus.BLOCKED, + StatusMessage = exception.Message, + Arguments = formattedArguments, + Result = exception.Message, + }); + } catch (Exception exception) { var error = $"Tool execution failed: {exception.Message}"; From 8c8b557828a430f412bff02299b02ea24263be24 Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 15:30:23 +0200 Subject: [PATCH 09/17] connecting allowed host settings to the ui --- .../Dialogs/Settings/ToolSettingsDialog.razor | 4 ++-- .../Settings/ToolSettingsDialog.razor.cs | 7 ++++++ .../ToolCallingSystem/ToolSettingsService.cs | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor index b41ee616..72e47385 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor @@ -26,7 +26,7 @@ var field = property.Value; if (field.EnumValues.Count > 0) { - + @foreach (var option in field.EnumValues) { @option @@ -35,7 +35,7 @@ } else { - + } } diff --git a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs index e4cf432c..30c88796 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs @@ -1,3 +1,4 @@ +using AIStudio.Settings; using AIStudio.Tools.ToolCallingSystem; using Microsoft.AspNetCore.Components; @@ -38,6 +39,12 @@ public partial class ToolSettingsDialog : SettingsDialogBase private string GetFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => this.implementation?.GetSettingsFieldDescription(fieldName, fieldDefinition) ?? fieldDefinition.Description; + private bool IsFieldDisabled(string fieldName) => + this.toolDefinition?.Id.Equals(ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, StringComparison.Ordinal) is true && + fieldName.Equals("allowedPrivateHosts", StringComparison.Ordinal) && + ManagedConfiguration.TryGet(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, out var meta) && + meta.IsLocked; + private void UpdateValue(string fieldName, string? value) => this.values[fieldName] = value ?? string.Empty; private async Task Save() diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs index fa1b592f..a1142913 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs @@ -5,6 +5,8 @@ namespace AIStudio.Tools.ToolCallingSystem; public sealed class ToolSettingsService(SettingsManager settingsManager, RustService rustService) { + private const string READ_WEB_PAGE_ALLOWED_PRIVATE_HOSTS_FIELD = "allowedPrivateHosts"; + public async Task> GetSettingsAsync(ToolDefinition definition) { var values = new Dictionary(StringComparer.Ordinal); @@ -13,6 +15,12 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer { var fieldName = property.Key; var fieldDefinition = property.Value; + if (IsReadWebPageAllowedPrivateHostsField(definition, fieldName)) + { + values[fieldName] = settingsManager.ConfigurationData.Tools.ReadWebPageAllowedPrivateHosts; + continue; + } + if (fieldDefinition.Secret) { var response = await rustService.GetSecret(new ToolSettingsSecretId(definition.Id, fieldName), isTrying: true); @@ -79,6 +87,14 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer values.TryGetValue(fieldName, out var value); value ??= string.Empty; + if (IsReadWebPageAllowedPrivateHostsField(definition, fieldName)) + { + if (!IsReadWebPageAllowedPrivateHostsLocked()) + settingsManager.ConfigurationData.Tools.ReadWebPageAllowedPrivateHosts = value; + + continue; + } + if (fieldDefinition.Secret) { var secretId = new ToolSettingsSecretId(definition.Id, fieldName); @@ -96,4 +112,11 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer await settingsManager.StoreSettings(); await MessageBus.INSTANCE.SendMessage(null, Event.CONFIGURATION_CHANGED, null); } + + private static bool IsReadWebPageAllowedPrivateHostsField(ToolDefinition definition, string fieldName) => + definition.Id.Equals(ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, StringComparison.Ordinal) && + fieldName.Equals(READ_WEB_PAGE_ALLOWED_PRIVATE_HOSTS_FIELD, StringComparison.Ordinal); + + private static bool IsReadWebPageAllowedPrivateHostsLocked() => + ManagedConfiguration.TryGet(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, out var meta) && meta.IsLocked; } From 948d5dec2784c8b5466898a2df33306ea25144b5 Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 15:31:13 +0200 Subject: [PATCH 10/17] including the allowed private hosts to the tool definition parameters --- .../wwwroot/tool_definitions/read_web_page.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/MindWork AI Studio/wwwroot/tool_definitions/read_web_page.json b/app/MindWork AI Studio/wwwroot/tool_definitions/read_web_page.json index e57d82be..21ea124d 100644 --- a/app/MindWork AI Studio/wwwroot/tool_definitions/read_web_page.json +++ b/app/MindWork AI Studio/wwwroot/tool_definitions/read_web_page.json @@ -16,6 +16,10 @@ "maxContentCharacters": { "type": "string", "secret": false + }, + "allowedPrivateHosts": { + "type": "string", + "secret": false } }, "required": [] From b1f50b7b5c7e947e8f702a92ed6f1c04d28efe97 Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 15:33:36 +0200 Subject: [PATCH 11/17] added the actual filtering and blocking security logic so that only public hosts will be allowed and only high confidence providers are used if request goes to allowed internal hosts --- app/MindWork AI Studio/Tools/HTMLParser.cs | 75 +++++-- .../ReadWebPageTool.cs | 205 +++++++++++++++++- 2 files changed, 256 insertions(+), 24 deletions(-) diff --git a/app/MindWork AI Studio/Tools/HTMLParser.cs b/app/MindWork AI Studio/Tools/HTMLParser.cs index 56aceee4..3e86e830 100644 --- a/app/MindWork AI Studio/Tools/HTMLParser.cs +++ b/app/MindWork AI Studio/Tools/HTMLParser.cs @@ -11,6 +11,7 @@ namespace AIStudio.Tools; public sealed class HTMLParser { private const string USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MindWorkAIStudio/1.0"; + private const int MAX_REDIRECTS = 10; private static readonly Config MARKDOWN_PARSER_CONFIG = new() { @@ -43,11 +44,12 @@ public sealed class HTMLParser return innerHtml; } - public async Task LoadWebPageAsync(Uri url, CancellationToken token = default, int timeoutSeconds = 30) + public async Task LoadWebPageAsync(Uri url, CancellationToken token = default, int timeoutSeconds = 30, Func? validateUrlAsync = null) { using var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli, + AllowAutoRedirect = false, }; using var httpClient = new HttpClient(handler) { @@ -55,7 +57,53 @@ public sealed class HTMLParser }; using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(token); timeoutCts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds)); - using var request = new HttpRequestMessage(HttpMethod.Get, url); + + var currentUrl = url; + for (var redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) + { + if (validateUrlAsync is not null) + await validateUrlAsync(currentUrl, timeoutCts.Token); + + using var request = CreateRequest(currentUrl); + using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeoutCts.Token); + if (IsRedirect(response.StatusCode)) + { + if (response.Headers.Location is null) + throw new HttpRequestException($"The server returned a redirect without a Location header for '{currentUrl}'.", null, response.StatusCode); + + currentUrl = response.Headers.Location.IsAbsoluteUri + ? response.Headers.Location + : new Uri(currentUrl, response.Headers.Location); + + continue; + } + + if (!response.IsSuccessStatusCode) + { + var statusCode = (int)response.StatusCode; + var reasonPhrase = string.IsNullOrWhiteSpace(response.ReasonPhrase) ? "Unknown" : response.ReasonPhrase; + throw new HttpRequestException($"The server returned HTTP {statusCode} ({reasonPhrase}) for '{currentUrl}'.", null, response.StatusCode); + } + + var html = await response.Content.ReadAsStringAsync(timeoutCts.Token); + var document = new HtmlDocument(); + document.LoadHtml(html); + + return new HTMLParserWebPage + { + RequestedUrl = url, + FinalUrl = response.RequestMessage?.RequestUri ?? currentUrl, + ContentType = response.Content.Headers.ContentType?.MediaType ?? string.Empty, + Document = document, + }; + } + + throw new HttpRequestException($"The server returned more than {MAX_REDIRECTS} redirects for '{url}'."); + } + + private static HttpRequestMessage CreateRequest(Uri url) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.TryAddWithoutValidation("User-Agent", USER_AGENT); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html")); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xhtml+xml")); @@ -69,28 +117,11 @@ public sealed class HTMLParser request.Headers.TryAddWithoutValidation("Sec-Fetch-Mode", "navigate"); request.Headers.TryAddWithoutValidation("Sec-Fetch-Dest", "document"); request.Headers.TryAddWithoutValidation("Sec-Fetch-User", "?1"); - - using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeoutCts.Token); - if (!response.IsSuccessStatusCode) - { - var statusCode = (int)response.StatusCode; - var reasonPhrase = string.IsNullOrWhiteSpace(response.ReasonPhrase) ? "Unknown" : response.ReasonPhrase; - throw new HttpRequestException($"The server returned HTTP {statusCode} ({reasonPhrase}) for '{url}'.", null, response.StatusCode); - } - - 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, - }; + return request; } + private static bool IsRedirect(HttpStatusCode statusCode) => (int)statusCode is >= 300 and <= 399; + public string ExtractTitle(HtmlDocument document) { var title = document.DocumentNode.SelectSingleNode("//title")?.InnerText?.Trim(); diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs index 4a9c997f..0920789c 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs @@ -1,17 +1,21 @@ +using System.Net; +using System.Net.Sockets; using System.Text.Json; using System.Text.Json.Nodes; +using AIStudio.Provider; using AIStudio.Tools.PluginSystem; using HtmlAgilityPack; namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations; -public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation +public sealed class ReadWebPageTool(HTMLParser htmlParser, ILogger logger) : IToolImplementation { private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); private const int DEFAULT_TIMEOUT_SECONDS = 30; private const int DEFAULT_MAX_CONTENT_CHARACTERS = 12000; private const int MAX_TRACE_LENGTH = 12000; + private const string ALLOWED_PRIVATE_HOSTS_SETTING = "allowedPrivateHosts"; private static readonly string[] REMOVED_NODE_XPATHS = [ @@ -42,6 +46,7 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation { "timeoutSeconds" => TB("Timeout Seconds"), "maxContentCharacters" => TB("Maximum Content Characters"), + ALLOWED_PRIVATE_HOSTS_SETTING => TB("Allowed Private Hosts"), _ => TB(fieldDefinition.Title), }; @@ -49,6 +54,7 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation { "timeoutSeconds" => TB("Optional HTTP timeout for loading a web page in seconds."), "maxContentCharacters" => TB("Optional global truncation limit for extracted Markdown returned to the model."), + ALLOWED_PRIVATE_HOSTS_SETTING => TB("Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider."), _ => TB(fieldDefinition.Description), }; @@ -75,6 +81,15 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation }); } + if (!TryReadAllowedPrivateHostPatterns(settingsValues.GetValueOrDefault(ALLOWED_PRIVATE_HOSTS_SETTING), out _, out var allowlistError)) + { + return Task.FromResult(new ToolConfigurationState + { + IsConfigured = false, + Message = allowlistError, + }); + } + return Task.FromResult(null); } @@ -86,11 +101,17 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation var timeoutSeconds = ReadOptionalPositiveIntSetting(context.SettingsValues, "timeoutSeconds") ?? DEFAULT_TIMEOUT_SECONDS; var maxContentCharacters = ReadOptionalPositiveIntSetting(context.SettingsValues, "maxContentCharacters") ?? DEFAULT_MAX_CONTENT_CHARACTERS; + if (!TryReadAllowedPrivateHostPatterns(context.SettingsValues.GetValueOrDefault(ALLOWED_PRIVATE_HOSTS_SETTING), out var allowedPrivateHosts, out var allowlistError)) + throw new InvalidOperationException(allowlistError); HTMLParserWebPage page; try { - page = await htmlParser.LoadWebPageAsync(url, token, timeoutSeconds); + page = await htmlParser.LoadWebPageAsync( + url, + token, + timeoutSeconds, + async (candidateUrl, validationToken) => await this.ValidateUrlAccessAsync(candidateUrl, allowedPrivateHosts, context.ProviderConfidence, validationToken)); } catch (OperationCanceledException) when (!token.IsCancellationRequested) { @@ -162,6 +183,178 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation return $"{rawResult[..MAX_TRACE_LENGTH]}..."; } + private async Task ValidateUrlAccessAsync( + Uri url, + IReadOnlyList allowedPrivateHosts, + ConfidenceLevel providerConfidence, + CancellationToken token) + { + if (url is not { Scheme: "http" or "https" }) + throw new ToolExecutionBlockedException("Only HTTP and HTTPS URLs are supported."); + + if (IsBlockedHostName(url.Host)) + throw new ToolExecutionBlockedException("Local web page URLs are not supported."); + + var addresses = await ResolveHostAddressesAsync(url, token); + if (addresses.Count == 0) + throw new InvalidOperationException($"The host '{url.Host}' did not resolve to an IP address."); + + if (addresses.Any(IsNeverAllowedAddress)) + throw new ToolExecutionBlockedException("Local, link-local, multicast, and unspecified network addresses are not supported."); + + if (!addresses.Any(IsNonPublicAddress)) + return; + + if (!IsAllowedPrivateHost(url.Host, allowedPrivateHosts)) + throw new ToolExecutionBlockedException("Private or local-network web page URLs are not supported unless their host is explicitly allowed."); + + if (providerConfidence >= ConfidenceLevel.HIGH) + return; + + await this.ReportPrivateHostProviderBlockAsync(url, providerConfidence); + throw new ToolExecutionBlockedException("This private or VPN web page requires a High-confidence provider."); + } + + private async Task ReportPrivateHostProviderBlockAsync(Uri url, ConfidenceLevel providerConfidence) + { + logger.LogWarning( + "Blocked read_web_page access to allowed private host '{Host}' because provider confidence '{ProviderConfidence}' is below HIGH.", + url.Host, + providerConfidence); + + await MessageBus.INSTANCE.SendError(new DataErrorMessage( + Icons.Material.Filled.Security, + TB("The web page was not loaded because private or VPN web pages require a High-confidence provider."))); + } + + private static async Task> ResolveHostAddressesAsync(Uri url, CancellationToken token) + { + if (IPAddress.TryParse(url.Host, out var parsedAddress)) + return [NormalizeAddress(parsedAddress)]; + + try + { + return (await Dns.GetHostAddressesAsync(url.DnsSafeHost, token)) + .Select(NormalizeAddress) + .ToList(); + } + catch (SocketException exception) + { + throw new InvalidOperationException($"The host '{url.Host}' could not be resolved: {exception.Message}", exception); + } + } + + private static IPAddress NormalizeAddress(IPAddress address) => address.IsIPv4MappedToIPv6 ? address.MapToIPv4() : address; + + private static bool IsBlockedHostName(string host) + { + var normalizedHost = NormalizeHost(host); + return normalizedHost is "localhost" || + normalizedHost.EndsWith(".localhost", StringComparison.Ordinal); + } + + private static bool IsAllowedPrivateHost(string host, IReadOnlyList allowedPrivateHosts) + { + var normalizedHost = NormalizeHost(host); + return allowedPrivateHosts.Any(pattern => pattern.IsMatch(normalizedHost)); + } + + private static string NormalizeHost(string host) => host.Trim().TrimEnd('.').ToLowerInvariant(); + + private static bool IsNeverAllowedAddress(IPAddress address) + { + address = NormalizeAddress(address); + if (IPAddress.IsLoopback(address)) + return true; + + if (address.AddressFamily is AddressFamily.InterNetwork) + { + var bytes = address.GetAddressBytes(); + return address.Equals(IPAddress.Any) || + bytes[0] is 0 or 127 or >= 224 || + (bytes[0] == 169 && bytes[1] == 254); + } + + if (address.AddressFamily is AddressFamily.InterNetworkV6) + { + return address.Equals(IPAddress.IPv6Any) || + address.Equals(IPAddress.IPv6None) || + address.Equals(IPAddress.IPv6Loopback) || + address.IsIPv6LinkLocal || + address.IsIPv6Multicast; + } + + return true; + } + + private static bool IsNonPublicAddress(IPAddress address) + { + address = NormalizeAddress(address); + if (IsNeverAllowedAddress(address)) + return true; + + if (address.AddressFamily is AddressFamily.InterNetwork) + { + var bytes = address.GetAddressBytes(); + return bytes[0] == 10 || // Private network: 10.0.0.0/8 + (bytes[0] == 100 && bytes[1] is >= 64 and <= 127) || // Carrier-grade NAT: 100.64.0.0/10 + (bytes[0] == 172 && bytes[1] is >= 16 and <= 31) || // Private network: 172.16.0.0/12 + (bytes[0] == 192 && bytes[1] == 168) || // Private network: 192.168.0.0/16 + (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 0) || // IETF protocol assignments: 192.0.0.0/24 + (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 2) || // Documentation range: 192.0.2.0/24 + (bytes[0] == 198 && bytes[1] is 18 or 19) || // Benchmark testing range: 198.18.0.0/15 + (bytes[0] == 198 && bytes[1] == 51 && bytes[2] == 100) || // Documentation range: 198.51.100.0/24 + (bytes[0] == 203 && bytes[1] == 0 && bytes[2] == 113); // Documentation range: 203.0.113.0/24 + } + + if (address.AddressFamily is AddressFamily.InterNetworkV6) + { + var bytes = address.GetAddressBytes(); + return (bytes[0] & 0xfe) == 0xfc || // Unique local addresses: fc00::/7 + address.IsIPv6SiteLocal; // Deprecated site-local addresses: fec0::/10 + } + + return true; + } + + private static bool TryReadAllowedPrivateHostPatterns( + string? rawValue, + out List patterns, + out string error) + { + patterns = []; + error = string.Empty; + + foreach (var rawPattern in SplitAllowedPrivateHostPatterns(rawValue)) + { + var pattern = NormalizeHost(rawPattern); + if (pattern.Contains("://", StringComparison.Ordinal) || pattern.Contains('/')) + { + error = TB("Allowed private hosts must be host names only, without scheme or path."); + return false; + } + + var isWildcard = pattern.StartsWith("*.", StringComparison.Ordinal); + var host = isWildcard ? pattern[2..] : pattern; + if (string.IsNullOrWhiteSpace(host) || Uri.CheckHostName(host) is UriHostNameType.Unknown) + { + error = string.Format(TB("Allowed private host '{0}' is not valid."), rawPattern); + return false; + } + + patterns.Add(new AllowedPrivateHostPattern(host, isWildcard)); + } + + patterns = patterns + .Distinct() + .ToList(); + return true; + } + + private static IEnumerable SplitAllowedPrivateHostPatterns(string? rawValue) => rawValue? + .Split(['\r', '\n', ',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Where(x => !string.IsNullOrWhiteSpace(x)) ?? []; + private static void RemoveNoiseNodes(HtmlNode rootNode) { foreach (var xpath in REMOVED_NODE_XPATHS) @@ -221,4 +414,12 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser) : IToolImplementation error = I18N.I.T($"The setting '{key}' must be a positive integer.", typeof(ReadWebPageTool).Namespace, nameof(ReadWebPageTool)); return false; } + + private readonly record struct AllowedPrivateHostPattern(string Host, bool IsWildcard) + { + public bool IsMatch(string normalizedHost) => + this.IsWildcard + ? normalizedHost.EndsWith($".{this.Host}", StringComparison.Ordinal) && normalizedHost.Length > this.Host.Length + 1 + : normalizedHost.Equals(this.Host, StringComparison.Ordinal); + } } From e50c67182c6940af401e31258ade1f28161d5c00 Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 15:34:55 +0200 Subject: [PATCH 12/17] translation --- .../Assistants/I18N/allTexts.lua | 15 ++++++ .../plugin.lua | 54 +++++++++++++++---- .../plugin.lua | 54 +++++++++++++++---- .../wwwroot/changelog/v26.3.1.md | 1 + 4 files changed, 106 insertions(+), 18 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index fbf478f4..f277aca0 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -6883,18 +6883,33 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS: -- Optional global truncation limit for extracted Markdown returned to the model. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optional global truncation limit for extracted Markdown returned to the model." +-- Allowed private hosts must be host names only, without scheme or path. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2196457612"] = "Allowed private hosts must be host names only, without scheme or path." + +-- Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T237631450"] = "Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider." + -- Maximum Content Characters UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters" -- Optional HTTP timeout for loading a web page in seconds. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optional HTTP timeout for loading a web page in seconds." +-- Allowed private host '{0}' is not valid. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3089707139"] = "Allowed private host '{0}' is not valid." + +-- Allowed Private Hosts +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3415515539"] = "Allowed Private Hosts" + -- Timeout Seconds UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Timeout Seconds" -- Read Web Page UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Read Web Page" +-- The web page was not loaded because private or VPN web pages require a High-confidence provider. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3856267430"] = "The web page was not loaded because private or VPN web pages require a High-confidence provider." + -- Maximum Results UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results" diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index f1430425..bae2e9aa 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -2760,9 +2760,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1688023907"] = "Werkzeug -- Enabling this tool also enables Read Web Page. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3023833839"] = "Das Aktivieren dieses Werkzeugs aktiviert auch „Webseite lesen“." --- This tool requires provider confidence {0}. The selected provider has {1}. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "Dieses Werkzeug erfordert Anbieter-Vertrauen {0}. Der ausgewählte Anbieter hat {1}." - -- Required settings are missing. Configure this tool before enabling it. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3119156561"] = "Erforderliche Einstellungen fehlen. Konfigurieren Sie dieses Tool, bevor Sie es aktivieren." @@ -2775,6 +2772,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Schließe -- No tools are available in this context. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "Keine Werkzeuge sind in diesem Kontext verfügbar." +-- This tool requires provider confidence {0}. The selected provider has {1}. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "Dieses Werkzeug erfordert Anbieter-Vertrauen {0}. Der ausgewählte Anbieter hat {1}." + -- Tool Selection UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Werkzeugauswahl" @@ -5931,18 +5931,15 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "Wir haben ve -- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Möglicherweise können Sie diesen Anbieter von Ihrem Standort aus nicht nutzen. Die Nachricht des Anbieters lautet: „{2}“." --- The tool calling request failed with status code {0}. The provider message is: '{1}' -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "Die Tool-Aufrufanfrage ist mit dem Statuscode {0} fehlgeschlagen. Die Meldung des Anbieters lautet: „{1}“" - --- The tool calling request failed with status code {0}. See the logs for details. -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "Die Tool-Aufrufanfrage ist mit dem Statuscode {0} fehlgeschlagen. Details finden Sie in den Logs." - -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Etwas wurde nicht gefunden. Die Nachricht des Anbieters lautet: „{2}“" -- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Selbst nach {2} erneuten Versuchen gab es weiterhin Probleme mit der Anfrage. Die Meldung des Anbieters lautet: „{3}“." +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "Die Tool-Aufrufanfrage ist mit dem Statuscode {0} fehlgeschlagen. Details finden Sie in den Logs." + -- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Dabei sind Probleme bei der Anfrage aufgetreten. Die Meldung des Anbieters lautet: '{1}'" @@ -5973,6 +5970,30 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3788466789"] = "Der Anbieter b -- The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T991875725"] = "Der Anbieter betreibt seinen Dienst von China aus. Im Verdachtsfall können Behörden in den jeweiligen Ländern auf ihre Daten zugreifen. **Ihre Daten werden jedoch nicht zum Trainieren** verwendet." +-- Medium +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T163471254"] = "Mittel" + +-- Moderate +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T177463328"] = "Mittel" + +-- Unknown confidence level +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T1811522309"] = "Unbekanntes Vertrauensniveau" + +-- No provider selected +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2897045472"] = "Kein Anbieter ausgewählt" + +-- Low +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2984088865"] = "Niedrig" + +-- Untrusted +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3063224793"] = "Nicht vertrauenswürdig" + +-- High +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3188327965"] = "Hoch" + +-- Very Low +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T786675843"] = "Sehr niedrig" + -- Self-hosted UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T146444217"] = "Selbst gehostet" @@ -6864,18 +6885,33 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS: -- Optional global truncation limit for extracted Markdown returned to the model. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optionales globales Kürzungslimit für extrahiertes Markdown, das an das Modell zurückgegeben wird." +-- Allowed private hosts must be host names only, without scheme or path. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2196457612"] = "Zulässige private Hosts dürfen nur Hostnamen enthalten, ohne Schema oder Pfad." + +-- Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T237631450"] = "Optionale Host-Zulassungsliste für private oder VPN-Webseiten. Trennen Sie Host-Muster mit Kommas, zum Beispiel example.de, *.example.de. Zugelassene private Hosts erfordern einen Anbieter mit hoher Vertrauensstufe." + -- Maximum Content Characters UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximale Inhaltszeichen" -- Optional HTTP timeout for loading a web page in seconds. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optionales HTTP-Zeitlimit zum Laden einer Webseite in Sekunden." +-- Allowed private host '{0}' is not valid. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3089707139"] = "Der zulässige private Host „{0}“ ist ungültig." + +-- Allowed Private Hosts +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3415515539"] = "Zulässige private Hosts" + -- Timeout Seconds UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Zeitlimit in Sekunden" -- Read Web Page UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Webseite lesen" +-- The web page was not loaded because private or VPN web pages require a High-confidence provider. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3856267430"] = "Die Webseite wurde nicht geladen, da private Webseiten oder Webseiten über ein VPN einen Anbieter mit hoher Vertrauensstufe erfordern." + -- Maximum Results UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximale Anzahl an Ergebnissen" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 28fe8a07..474cff8a 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -2760,9 +2760,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1688023907"] = "Tool chan -- Enabling this tool also enables Read Web Page. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3023833839"] = "Enabling this tool also enables Read Web Page." --- This tool requires provider confidence {0}. The selected provider has {1}. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "This tool requires provider confidence {0}. The selected provider has {1}." - -- 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." @@ -2775,6 +2772,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Close" -- No tools are available in this context. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "No tools are available in this context." +-- This tool requires provider confidence {0}. The selected provider has {1}. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "This tool requires provider confidence {0}. The selected provider has {1}." + -- Tool Selection UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Tool Selection" @@ -5931,18 +5931,15 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to -- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'" --- The tool calling request failed with status code {0}. The provider message is: '{1}' -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2584985559"] = "The tool calling request failed with status code {0}. The provider message is: '{1}'" - --- The tool calling request failed with status code {0}. See the logs for details. -UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details." - -- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'" -- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'." +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details." + -- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}' UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'" @@ -5973,6 +5970,30 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3788466789"] = "The provider o -- The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T991875725"] = "The provider operates its service from China. In case of suspicion, authorities in the respective countries of operation may access your data. However, **your data is not used for training** purposes." +-- Medium +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T163471254"] = "Medium" + +-- Moderate +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T177463328"] = "Moderate" + +-- Unknown confidence level +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T1811522309"] = "Unknown confidence level" + +-- No provider selected +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2897045472"] = "No provider selected" + +-- Low +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T2984088865"] = "Low" + +-- Untrusted +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3063224793"] = "Untrusted" + +-- High +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T3188327965"] = "High" + +-- Very Low +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCELEVELEXTENSIONS::T786675843"] = "Very Low" + -- Self-hosted UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T146444217"] = "Self-hosted" @@ -6864,18 +6885,33 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS: -- Optional global truncation limit for extracted Markdown returned to the model. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optional global truncation limit for extracted Markdown returned to the model." +-- Allowed private hosts must be host names only, without scheme or path. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2196457612"] = "Allowed private hosts must be host names only, without scheme or path." + +-- Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T237631450"] = "Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider." + -- Maximum Content Characters UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters" -- Optional HTTP timeout for loading a web page in seconds. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optional HTTP timeout for loading a web page in seconds." +-- Allowed private host '{0}' is not valid. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3089707139"] = "Allowed private host '{0}' is not valid." + +-- Allowed Private Hosts +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3415515539"] = "Allowed Private Hosts" + -- Timeout Seconds UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Timeout Seconds" -- Read Web Page UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Read Web Page" +-- The web page was not loaded because private or VPN web pages require a High-confidence provider. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3856267430"] = "The web page was not loaded because private or VPN web pages require a High-confidence provider." + -- Maximum Results UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results" diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index ddf9a890..209c0774 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -21,6 +21,7 @@ - Improved the logbook reliability by significantly reducing duplicate log entries. - Improved file attachments in chats: configuration and project files such as `Dockerfile`, `Caddyfile`, `Makefile`, or `Jenkinsfile` are now included more reliably when you send them to the AI. - Improved the validation of additional API parameters in the advanced provider settings to help catch formatting mistakes earlier. +- Improved the web page reader with stronger protection for private network pages and administrator-configurable allowlists for trusted intranet domains. - Improved the app startup resilience by allowing AI Studio to continue without Qdrant if it fails to initialize. - Improved the translation assistant by updating the system and user prompts. - Fixed an issue where assistants hidden via configuration plugins still appear in "Send to ..." menus. Thanks, Gunnar, for reporting this issue. From cf6256c215ddb8e01faff003f190bf12c8d08efa Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Mon, 18 May 2026 17:00:05 +0200 Subject: [PATCH 13/17] Resolve target host addresses before connecting, then bind HTTP connection to those validated IPs. Prevents request from re-resolving the host after validation --- app/MindWork AI Studio/Tools/HTMLParser.cs | 70 +++++++++++++++++-- .../ReadWebPageTool.cs | 28 ++++++-- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/app/MindWork AI Studio/Tools/HTMLParser.cs b/app/MindWork AI Studio/Tools/HTMLParser.cs index 3e86e830..fb5334ea 100644 --- a/app/MindWork AI Studio/Tools/HTMLParser.cs +++ b/app/MindWork AI Studio/Tools/HTMLParser.cs @@ -1,9 +1,7 @@ using System.Net; -using System.Net.Http; using System.Net.Http.Headers; - +using System.Net.Sockets; using HtmlAgilityPack; - using ReverseMarkdown; namespace AIStudio.Tools; @@ -44,13 +42,20 @@ public sealed class HTMLParser return innerHtml; } - public async Task LoadWebPageAsync(Uri url, CancellationToken token = default, int timeoutSeconds = 30, Func? validateUrlAsync = null) + public async Task LoadWebPageAsync(Uri url, CancellationToken token = default, int timeoutSeconds = 30, Func>>? resolveUrlAddressesAsync = null) { - using var handler = new HttpClientHandler + using var handler = new SocketsHttpHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli, AllowAutoRedirect = false, }; + if (resolveUrlAddressesAsync is not null) + { + // The callback binds the request to a vetted target IP; a proxy would change the endpoint being connected to. + handler.UseProxy = false; + handler.ConnectCallback = async (context, connectionToken) => await ConnectToResolvedAddressAsync(context, resolveUrlAddressesAsync, connectionToken); + } + using var httpClient = new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan, @@ -61,8 +66,7 @@ public sealed class HTMLParser var currentUrl = url; for (var redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) { - if (validateUrlAsync is not null) - await validateUrlAsync(currentUrl, timeoutCts.Token); + ValidateHttpOrHttpsUrl(currentUrl); using var request = CreateRequest(currentUrl); using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeoutCts.Token); @@ -101,6 +105,58 @@ public sealed class HTMLParser throw new HttpRequestException($"The server returned more than {MAX_REDIRECTS} redirects for '{url}'."); } + private static void ValidateHttpOrHttpsUrl(Uri url) + { + if (url.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || + url.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) + return; + + throw new HttpRequestException($"Unsupported URL scheme '{url.Scheme}' for '{url}'."); + } + + private static async ValueTask ConnectToResolvedAddressAsync( + SocketsHttpConnectionContext context, + Func>> resolveUrlAddressesAsync, + CancellationToken token) + { + var requestUri = context.InitialRequestMessage.RequestUri ?? + throw new HttpRequestException("The HTTP request did not contain a target URL."); + + var addresses = await resolveUrlAddressesAsync(requestUri, token); + if (addresses.Count == 0) + throw new HttpRequestException($"The host '{requestUri.Host}' did not resolve to an IP address."); + + List connectionErrors = []; + foreach (var address in addresses.Distinct()) + { + var socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp) + { + NoDelay = true, + }; + + try + { + await socket.ConnectAsync(new IPEndPoint(address, context.DnsEndPoint.Port), token); + return new NetworkStream(socket, ownsSocket: true); + } + catch (SocketException exception) + { + connectionErrors.Add(exception); + socket.Dispose(); + } + catch + { + socket.Dispose(); + throw; + } + } + + Exception innerException = connectionErrors.Count == 1 + ? connectionErrors[0] + : new AggregateException(connectionErrors); + throw new HttpRequestException($"Could not connect to a validated address for '{requestUri.Host}'.", innerException); + } + private static HttpRequestMessage CreateRequest(Uri url) { var request = new HttpRequestMessage(HttpMethod.Get, url); diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs index 0920789c..4098d9ac 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs @@ -111,7 +111,7 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser, ILogger await this.ValidateUrlAccessAsync(candidateUrl, allowedPrivateHosts, context.ProviderConfidence, validationToken)); + async (candidateUrl, validationToken) => await this.ResolveValidatedUrlAddressesAsync(candidateUrl, allowedPrivateHosts, context.ProviderConfidence, validationToken)); } catch (OperationCanceledException) when (!token.IsCancellationRequested) { @@ -119,6 +119,9 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser, ILogger> ResolveValidatedUrlAddressesAsync( Uri url, IReadOnlyList allowedPrivateHosts, ConfidenceLevel providerConfidence, @@ -203,13 +223,13 @@ public sealed class ReadWebPageTool(HTMLParser htmlParser, ILogger= ConfidenceLevel.HIGH) - return; + return addresses; await this.ReportPrivateHostProviderBlockAsync(url, providerConfidence); throw new ToolExecutionBlockedException("This private or VPN web page requires a High-confidence provider."); From f8ba55bcd96ec2c8c45f289535c59dd1c6ffc2c9 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Mon, 18 May 2026 20:10:53 +0200 Subject: [PATCH 14/17] included documentation how to add tools --- AGENTS.md | 15 +- README.md | 4 +- .../Plugins/configuration/plugin.lua | 7 +- .../wwwroot/changelog/v26.3.1.md | 2 + documentation/Tools.md | 176 ++++++++++++++++++ 5 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 documentation/Tools.md diff --git a/AGENTS.md b/AGENTS.md index 6bf4eb5f..528236f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,6 +119,19 @@ When adding configuration options, update: - `app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs` for parsing logic of complex configuration objects. - `app/MindWork AI Studio/Plugins/configuration/plugin.lua` to document the new configuration option. +## Tool Calling System + +**Documentation:** `documentation/Tools.md` + +When adding, changing, or removing model-driven tools, keep these parts in sync: +- `app/MindWork AI Studio/wwwroot/tool_definitions/` for the tool JSON definition. +- `app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/` for the `IToolImplementation` class. +- `app/MindWork AI Studio/Program.cs` for DI registration of the implementation. +- `app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs` when default tool dependencies or minimum provider confidence rules change. +- `app/MindWork AI Studio/Plugins/configuration/plugin.lua` when administrators can configure or manage the tool or its settings. + +Tool implementations must treat model-provided arguments as untrusted input. Validate settings and arguments, protect secrets with `SensitiveTraceArgumentNames`, use `ToolExecutionBlockedException` for intentional policy blocks, and check provider confidence before returning sensitive data to the model. + ## RAG (Retrieval-Augmented Generation) RAG integration is currently in development (preview feature). Architecture: @@ -214,4 +227,4 @@ following words: - Downgraded - Upgraded -The entire changelog is sorted by these categories in the order shown above. The language used for the changelog is US English. \ No newline at end of file +The entire changelog is sorted by these categories in the order shown above. The language used for the changelog is US English. diff --git a/README.md b/README.md index a594ff41..7a097881 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,8 @@ If you're interested in learning more about future plans, check out our [roadmap You want to know how to build MindWork AI Studio from source? [Check out the instructions here](documentation/Build.md). +Do you want to add or maintain model-driven tools? [Read the tool development guide here](documentation/Tools.md). +
@@ -213,4 +215,4 @@ MindWork AI Studio is licensed under the `FSL-1.1-MIT` license (functional sourc For more details, refer to the [LICENSE](LICENSE.md) file. This license structure ensures you have plenty of freedom to use and enjoy the software while protecting our work. -
\ No newline at end of file + diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 419e804f..91ec4af1 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -213,13 +213,12 @@ CONFIG["SETTINGS"] = {} -- CONFIG["SETTINGS"]["DataApp.ShortcutVoiceRecording"] = "CmdOrControl+1" -- Configure the minimum provider confidence level required for individual tools. --- Tool IDs include: web_search, read_web_page, get_current_weather +-- Tool IDs include: web_search, read_web_page -- Allowed values are: NONE, UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH --- Defaults: web_search = MEDIUM, read_web_page = MEDIUM, get_current_weather = NONE +-- Defaults: web_search = MEDIUM, read_web_page = MEDIUM, but higher confidence is recommended -- CONFIG["SETTINGS"]["DataTools.MinimumProviderConfidenceByToolId"] = { -- ["web_search"] = "MEDIUM", --- ["read_web_page"] = "MEDIUM", --- ["get_current_weather"] = "NONE" +-- ["read_web_page"] = "MEDIUM" -- } -- Configure private or VPN hosts that the Read Web Page tool may access. diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index 209c0774..26bb30b0 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -8,6 +8,7 @@ - Added math rendering in chats for LaTeX display formulas, including block formats such as `$$ ... $$` and `\[ ... \]`. - Added the latest OpenAI models. - Added minimum provider confidence settings for tools, so sensitive tools such as web search can be limited to trusted providers. Configuration plugins can also manage these defaults for organizations. +- Added documentation for developers who want to add or maintain tools in AI Studio. - Released the document analysis assistant after an intense testing phase. - Improved enterprise deployment for organizations: administrators can now provide up to 10 centrally managed enterprise configuration slots, use policy files on Linux and macOS, and continue using older configuration formats as a fallback during migration. - Improved the profile selection for assistants and the chat. You can now explicitly choose between the app default profile, no profile, or a specific profile. @@ -30,3 +31,4 @@ - Fixed security issues in the native app runtime by strengthening how AI Studio creates and protects the secret values used for its internal secure connection. - Updated several security-sensitive Rust dependencies in the native runtime to address known vulnerabilities. - Updated .NET to v9.0.14 +- Removed the internal demo weather tool from the tool list. diff --git a/documentation/Tools.md b/documentation/Tools.md new file mode 100644 index 00000000..24269dfd --- /dev/null +++ b/documentation/Tools.md @@ -0,0 +1,176 @@ +# Tool Development + +This document explains how model-driven tools are added to AI Studio. Tool calling let a model request a small, well-defined action during a chat or assistant run, such as searching the web or reading a web page. + +Tools are part of the .NET app. They are not Lua plugins and they are not loaded dynamically from user folders. Adding a tool requires code changes. + +## Architecture + +A tool has two parts: + +- A JSON definition in `app/MindWork AI Studio/wwwroot/tool_definitions/` +- A C# implementation of `IToolImplementation` in `app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/` + +At startup, `ToolRegistry` reads all JSON definitions and matches each definition to a registered implementation by `implementationKey`. `ToolExecutor` runs the implementation when a provider returns a matching function call. + +The provider only sees tools that are available for the current component, selected by the user or defaults, supported by the model, configured correctly, and allowed by the provider confidence rules. + +## Definition File + +Create one JSON file per tool under `wwwroot/tool_definitions`. The file describes the user-visible tool metadata, optional settings, and the function schema sent to the model. + +Example: + +```json +{ + "schemaVersion": 1, + "id": "get_current_weather", + "implementationKey": "get_current_weather", + "visibleIn": { + "chat": true, + "assistants": true + }, + "settingsSchema": { + "type": "object", + "properties": { + "demoLabel": { + "type": "string", + "secret": false + } + }, + "required": [ + "demoLabel" + ] + }, + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location.", + "strict": true, + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The city to find the weather for, e.g. 'San Francisco'." + }, + "state": { + "type": "string", + "description": "The two-letter abbreviation for the state, e.g. 'CA'." + }, + "unit": { + "type": "string", + "description": "The unit to fetch the temperature in.", + "enum": [ + "celsius", + "fahrenheit" + ] + } + }, + "required": [ + "city", + "state", + "unit" + ], + "additionalProperties": false + } + } +} +``` + +Use stable lower-case IDs with underscores. Keep `id`, `implementationKey`, and `function.name` identical unless there is a clear compatibility reason not to. + +## Implementation + +Implement `IToolImplementation` and register the class in `Program.cs` as an `IToolImplementation`. + +Example: + +```csharp +using System.Text.Json; +using AIStudio.Tools.PluginSystem; + +namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations; + +public sealed class GetCurrentWeatherTool : IToolImplementation +{ + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); + + public string ImplementationKey => "get_current_weather"; + + public string Icon => Icons.Material.Filled.Cloud; + + public IReadOnlySet SensitiveTraceArgumentNames => new HashSet(StringComparer.Ordinal); + + public string GetDisplayName() => TB("Current Weather"); + + public string GetDescription() => TB("Use this demo tool to retrieve the current weather for a given city and state."); + + public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch + { + "demoLabel" => TB("Demo Label"), + _ => TB(fieldDefinition.Title), + }; + + public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch + { + "demoLabel" => TB("Required demo setting for validating tool settings."), + _ => TB(fieldDefinition.Description), + }; + + public Task 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")) + throw new ArgumentException($"Invalid unit '{unit}'."); + + return Task.FromResult(new ToolExecutionResult + { + TextContent = $"The weather in {city}, {state} is 85 degrees {unit}.", + }); + } +} +``` + +Register it: + +```csharp +builder.Services.AddSingleton(); +``` + +The example above is documentation-only. Do not keep demo tools in the production tool catalog. + +## Settings And Secrets + +Tool settings are stored through `ToolSettingsService`. Plain settings are stored in the regular configuration data. Settings marked with `"secret": true` are stored in the OS keyring through the Rust service. + +Use `ValidateConfigurationAsync` when a setting needs more than "required field is present" validation, such as URL syntax, numeric limits, mutually exclusive options, or allowlist parsing. + +Use `SensitiveTraceArgumentNames` for model-provided arguments that must not be shown in tool traces. Do not return secrets in `TextContent`, `JsonContent`, exception messages, logs, or trace formatting. + +## Security + +Treat model-provided tool arguments as untrusted input. + +For tools that perform network requests: + +- Accept only the schemes and hosts that are required for the feature. +- Validate redirects before following them. +- Do not allow model-supplied URLs to access localhost, loopback, link-local, multicast, or private network targets unless the feature has an explicit policy for that. +- Check `ToolExecutionContext.ProviderConfidence` before returning sensitive data to the model. +- Throw `ToolExecutionBlockedException` for intentional policy blocks so the UI can show the call as blocked instead of failed. + +For settings that administrators should be able to manage centrally, add the setting to the appropriate `Settings/DataModel` class, register it with `ManagedConfiguration.Register(...)`, process it in `PluginConfiguration`, clean leftovers in `PluginFactory.Loading`, and document it in `Plugins/configuration/plugin.lua`. + +## Checklist + +- Add the JSON definition in `wwwroot/tool_definitions`. +- Add the `IToolImplementation` class. +- Register the implementation in `Program.cs`. +- Validate settings and model arguments. +- Protect secrets and sensitive trace arguments. +- Add provider-confidence checks when tool output may contain sensitive data. +- Update configuration plugin documentation when admins can manage the setting. +- Add a changelog entry when users or administrators are affected. From 9b638b8a5ad691bd96ca350be09844ad00244852 Mon Sep 17 00:00:00 2001 From: krut_ni Date: Mon, 18 May 2026 20:11:24 +0200 Subject: [PATCH 15/17] removed weather demo tool --- app/MindWork AI Studio/Program.cs | 1 - .../GetCurrentWeatherTool.cs | 46 ---------------- .../tool_definitions/get_current_weather.json | 53 ------------------- 3 files changed, 100 deletions(-) delete mode 100644 app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs delete mode 100644 app/MindWork AI Studio/wwwroot/tool_definitions/get_current_weather.json diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 0effbcc5..c7d22423 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -170,7 +170,6 @@ internal sealed class Program builder.Services.AddMudMarkdownClipboardService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs deleted file mode 100644 index 23bce8f3..00000000 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/GetCurrentWeatherTool.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text.Json; -using AIStudio.Tools.PluginSystem; - -namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations; - -public sealed class GetCurrentWeatherTool : IToolImplementation -{ - private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(GetCurrentWeatherTool).Namespace, nameof(GetCurrentWeatherTool)); - - public string ImplementationKey => "get_current_weather"; - - public string Icon => Icons.Material.Filled.Cloud; - - public IReadOnlySet SensitiveTraceArgumentNames => new HashSet(StringComparer.Ordinal); - - public string GetDisplayName() => TB("Current Weather"); - - public string GetDescription() => TB("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."); - - public string GetSettingsFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch - { - "demoLabel" => TB("Demo Label"), - _ => TB(fieldDefinition.Title), - }; - - public string GetSettingsFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => fieldName switch - { - "demoLabel" => TB("Required demo setting for validating tool settings in tests. It does not affect the weather result."), - _ => TB(fieldDefinition.Description), - }; - - public Task 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")) - throw new ArgumentException($"Invalid unit '{unit}'."); - - return Task.FromResult(new ToolExecutionResult - { - TextContent = $"The weather in {city}, {state} is 85 degrees {unit}. It is partly cloudy with highs in the 90's.", - }); - } -} diff --git a/app/MindWork AI Studio/wwwroot/tool_definitions/get_current_weather.json b/app/MindWork AI Studio/wwwroot/tool_definitions/get_current_weather.json deleted file mode 100644 index 47b93580..00000000 --- a/app/MindWork AI Studio/wwwroot/tool_definitions/get_current_weather.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "schemaVersion": 1, - "id": "get_current_weather", - "implementationKey": "get_current_weather", - "visibleIn": { - "chat": true, - "assistants": true - }, - "settingsSchema": { - "type": "object", - "properties": { - "demoLabel": { - "type": "string", - "secret": false - } - }, - "required": [ - "demoLabel" - ] - }, - "function": { - "name": "get_current_weather", - "description": "Get the current weather in a given location.", - "strict": true, - "parameters": { - "type": "object", - "properties": { - "city": { - "type": "string", - "description": "The city to find the weather for, e.g. 'San Francisco'." - }, - "state": { - "type": "string", - "description": "The two-letter abbreviation for the state, e.g. 'CA'." - }, - "unit": { - "type": "string", - "description": "The unit to fetch the temperature in.", - "enum": [ - "celsius", - "fahrenheit" - ] - } - }, - "required": [ - "city", - "state", - "unit" - ], - "additionalProperties": false - } - } -} From d380c3131a6df899ec6eb276f0b4c02cfc995e9d Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Tue, 19 May 2026 22:26:20 +0200 Subject: [PATCH 16/17] added response api and set as default next to the chat completion api --- .../Provider/BaseProvider.cs | 12 +- .../Provider/OpenAI/ProviderOpenAI.cs | 197 +++++++++++++++++- .../Provider/OpenAI/ProviderToolAdapters.cs | 35 ++++ .../Provider/OpenAI/ResponsesAPIRequest.cs | 8 +- .../OpenAI/ResponsesFunctionCallItem.cs | 15 ++ .../OpenAI/ResponsesFunctionCallOutputItem.cs | 13 ++ .../Provider/OpenAI/ResponsesFunctionTool.cs | 19 ++ .../Provider/OpenAI/ResponsesResponse.cs | 62 ++++++ .../Tools/ToolCallingSystem/ToolRegistry.cs | 3 +- .../wwwroot/changelog/v26.3.1.md | 1 + documentation/Tools.md | 34 +++ 11 files changed, 374 insertions(+), 25 deletions(-) create mode 100644 app/MindWork AI Studio/Provider/OpenAI/ProviderToolAdapters.cs create mode 100644 app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallItem.cs create mode 100644 app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallOutputItem.cs create mode 100644 app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionTool.cs create mode 100644 app/MindWork AI Studio/Provider/OpenAI/ResponsesResponse.cs diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 7b44eb9c..08821c3b 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -634,17 +634,7 @@ public abstract class BaseProvider : IProvider, ISecretId if (runnableTools.Count > 0) { - var providerTools = runnableTools.Select(x => (object)new - { - type = "function", - function = new - { - name = x.Definition.Function.Name, - description = x.Definition.Function.Description, - parameters = x.Definition.Function.Parameters, - strict = x.Definition.Function.Strict, - } - }).ToList(); + var providerTools = runnableTools.Select(x => ProviderToolAdapters.ToChatCompletionTool(x.Definition)).ToList(); var internalMessages = new List(); var toolCallCount = 0; diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index 3d7d280e..db48525c 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -5,7 +5,12 @@ using System.Text.Json; using AIStudio.Chat; using AIStudio.Settings; +using AIStudio.Tools.PluginSystem; +using AIStudio.Tools.Rust; using AIStudio.Tools.ToolCallingSystem; +using AIStudio.Tools.Services; + +using Microsoft.Extensions.DependencyInjection; namespace AIStudio.Provider.OpenAI; @@ -15,6 +20,7 @@ namespace AIStudio.Provider.OpenAI; public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https://api.openai.com/v1/", LOGGER) { private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderOpenAI).Namespace, nameof(ProviderOpenAI)); #region Implementation of IProvider @@ -64,12 +70,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https // Check if we are using the Responses API or the Chat Completion API: var usingResponsesAPI = modelCapabilities.Contains(Capability.RESPONSES_API); - var useChatCompletionsForTools = - chatThread.RuntimeSelectedToolIds.Count > 0 && - modelCapabilities.Contains(Capability.CHAT_COMPLETION_API) && - modelCapabilities.Contains(Capability.FUNCTION_CALLING); - if (useChatCompletionsForTools) - usingResponsesAPI = false; // Prepare the request path based on the API we are using: var requestPath = usingResponsesAPI ? "responses" : "chat/completions"; @@ -82,7 +82,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https var providerConfidence = this.Provider.GetConfidence(settingsManager).Level; var minimumWebSearchConfidence = settingsManager.GetMinimumProviderConfidenceForTool(ToolSelectionRules.WEB_SEARCH_TOOL_ID); var isWebSearchAllowed = ToolSelectionRules.IsProviderConfidenceAllowed(providerConfidence, minimumWebSearchConfidence); - IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) && isWebSearchAllowed + IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) && isWebSearchAllowed ? [ ProviderTools.WEB_SEARCH ] : []; @@ -166,6 +166,43 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https ? $"data:{attachment.DetermineMimeType()};base64,{base64Content}" : string.Empty, }); + + var baseInput = new List { systemPrompt }; + baseInput.AddRange(messages.Cast()); + + var toolRegistry = Program.SERVICE_PROVIDER.GetService(); + var toolExecutor = Program.SERVICE_PROVIDER.GetService(); + var currentAssistantContent = chatThread.Blocks.LastOrDefault(x => x.Role is ChatRole.AI)?.Content as ContentText; + currentAssistantContent?.ToolInvocations.Clear(); + + IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools = toolRegistry is null + ? [] + : await toolRegistry.GetRunnableToolsAsync( + chatThread.RuntimeComponent, + chatThread.RuntimeSelectedToolIds, + modelCapabilities, + providerConfidence, + settingsManager.IsToolSelectionVisible(chatThread.RuntimeComponent)); + + if (usingResponsesAPI && toolExecutor is not null && runnableTools.Count > 0) + { + await foreach (var content in this.StreamResponsesWithLocalTools( + chatModel, + baseInput, + apiParameters, + runnableTools, + toolExecutor, + currentAssistantContent, + requestedSecret, + providerConfidence, + token)) + yield return content; + + yield break; + } + + if (runnableTools.Count > 0) + providerTools = []; // // Create the request: either for the Responses API or the Chat Completion API @@ -191,7 +228,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https Model = chatModel.Id, // All messages go into the input field: - Input = [systemPrompt, ..messages], + Input = baseInput, // Right now, we only support streaming completions: Stream = true, @@ -200,7 +237,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https Store = false, // Tools we want to use: - ProviderTools = providerTools, + Tools = providerTools, // Additional API parameters: AdditionalApiParameters = apiParameters @@ -230,6 +267,148 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https yield return content; } + private async IAsyncEnumerable StreamResponsesWithLocalTools( + Model chatModel, + IList baseInput, + IDictionary apiParameters, + IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools, + ToolExecutor toolExecutor, + ContentText? currentAssistantContent, + RequestedSecret requestedSecret, + ConfidenceLevel providerConfidence, + [EnumeratorCancellation] CancellationToken token) + { + var providerTools = runnableTools + .Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)) + .ToList(); + var internalItems = new List(); + var toolCallCount = 0; + + while (true) + { + var requestDto = new ResponsesAPIRequest + { + Model = chatModel.Id, + Input = [..baseInput, ..internalItems], + Stream = false, + Store = false, + Tools = providerTools, + AdditionalApiParameters = apiParameters, + }; + var response = await this.ExecuteResponsesRequest(requestDto, requestedSecret, token); + if (response is null) + { + if (currentAssistantContent is not null) + { + currentAssistantContent.ToolRuntimeStatus = new(); + await currentAssistantContent.StreamingEvent(); + } + + yield break; + } + + var functionCalls = response.GetFunctionCalls(); + if (functionCalls.Count == 0) + { + if (currentAssistantContent is not null) + { + currentAssistantContent.ToolRuntimeStatus = new(); + await currentAssistantContent.StreamingEvent(); + } + + var textOutput = response.GetTextOutput(); + if (!string.IsNullOrWhiteSpace(textOutput)) + yield return new ContentStreamChunk(textOutput, []); + else if (toolCallCount > 0) + yield return new ContentStreamChunk("The model completed the tool call but did not return a final answer.", []); + + yield break; + } + + if (currentAssistantContent is not null) + { + currentAssistantContent.ToolRuntimeStatus = new ToolRuntimeStatus + { + IsRunning = true, + ToolNames = functionCalls + .Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Name) + .ToList(), + }; + await currentAssistantContent.StreamingEvent(); + } + + foreach (var outputItem in response.Output) + internalItems.Add(outputItem); + + foreach (var functionCall in functionCalls) + { + toolCallCount++; + if (toolCallCount > 10) + { + var limitMessage = "Tool calling stopped because the maximum of 10 tool calls was reached."; + currentAssistantContent?.ToolInvocations.Add(new ToolInvocationTrace + { + Order = toolCallCount, + ToolId = functionCall.Name, + ToolName = functionCall.Name, + ToolCallId = functionCall.CallId, + Status = ToolInvocationTraceStatus.BLOCKED, + StatusMessage = limitMessage, + Result = limitMessage, + }); + + if (currentAssistantContent is not null) + { + currentAssistantContent.ToolRuntimeStatus = new(); + await currentAssistantContent.StreamingEvent(); + } + + yield return new ContentStreamChunk(limitMessage, []); + yield break; + } + + var (toolContent, trace) = await toolExecutor.ExecuteAsync( + functionCall.CallId, + functionCall.Name, + functionCall.Arguments, + runnableTools, + providerConfidence, + toolCallCount, + token); + + currentAssistantContent?.ToolInvocations.Add(trace); + internalItems.Add(new ResponsesFunctionCallOutputItem + { + CallId = functionCall.CallId, + Output = toolContent, + }); + } + + if (currentAssistantContent is not null) + await currentAssistantContent.StreamingEvent(); + } + } + + private async Task ExecuteResponsesRequest(ResponsesAPIRequest requestDto, RequestedSecret requestedSecret, CancellationToken token) + { + using var request = new HttpRequestMessage(HttpMethod.Post, "responses"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + request.Content = new StringContent(JsonSerializer.Serialize(requestDto, JSON_SERIALIZER_OPTIONS), Encoding.UTF8, "application/json"); + + using var response = await this.httpClient.SendAsync(request, token); + if (!response.IsSuccessStatusCode) + { + var responseBody = await response.Content.ReadAsStringAsync(token); + LOGGER.LogError("Tool calling Responses API request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody); + await MessageBus.INSTANCE.SendError(new( + Icons.Material.Filled.Build, + string.Format(TB("The tool calling request failed with status code {0}. See the logs for details."), (int)response.StatusCode))); + return null; + } + + return await response.Content.ReadFromJsonAsync(JSON_SERIALIZER_OPTIONS, token); + } + #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderToolAdapters.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderToolAdapters.cs new file mode 100644 index 00000000..5c426ae1 --- /dev/null +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderToolAdapters.cs @@ -0,0 +1,35 @@ +using AIStudio.Tools.ToolCallingSystem; + +namespace AIStudio.Provider.OpenAI; + +/// +/// Converts the canonical AI Studio tool definition into provider-specific wire shapes. +/// +public static class ProviderToolAdapters +{ + /// + /// Builds the nested function tool shape used by Chat Completions compatible APIs. + /// + public static object ToChatCompletionTool(ToolDefinition definition) => new + { + type = "function", + function = new + { + name = definition.Function.Name, + description = definition.Function.Description, + parameters = definition.Function.Parameters, + strict = definition.Function.Strict, + } + }; + + /// + /// Builds the flat function tool shape used by the OpenAI Responses API. + /// + public static ResponsesFunctionTool ToResponsesTool(ToolDefinition definition) => new() + { + Name = definition.Function.Name, + Description = definition.Function.Description, + Parameters = definition.Function.Parameters, + Strict = definition.Function.Strict, + }; +} diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs index 739ad7ad..148edc79 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs @@ -6,16 +6,16 @@ namespace AIStudio.Provider.OpenAI; /// The request body for the Responses API. /// /// Which model to use. -/// The chat messages. +/// The chat messages and Responses API input items. /// Whether to stream the response. /// Whether to store the response on the server (usually OpenAI's infrastructure). -/// The provider-side tools to use for the request. +/// The provider-side tools and local function tools to use for the request. public record ResponsesAPIRequest( string Model, - IList Input, + IList Input, bool Stream, bool Store, - [property: JsonPropertyName("tools")] IList ProviderTools) + IList Tools) { public ResponsesAPIRequest() : this(string.Empty, [], true, false, []) { diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallItem.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallItem.cs new file mode 100644 index 00000000..25114a76 --- /dev/null +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallItem.cs @@ -0,0 +1,15 @@ +namespace AIStudio.Provider.OpenAI; + +/// +/// A function call item returned by the OpenAI Responses API. +/// +public sealed record ResponsesFunctionCallItem +{ + public string Type { get; init; } = string.Empty; + + public string CallId { get; init; } = string.Empty; + + public string Name { get; init; } = string.Empty; + + public string Arguments { get; init; } = string.Empty; +} diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallOutputItem.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallOutputItem.cs new file mode 100644 index 00000000..19e9bedb --- /dev/null +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionCallOutputItem.cs @@ -0,0 +1,13 @@ +namespace AIStudio.Provider.OpenAI; + +/// +/// A local function result item sent back to the OpenAI Responses API. +/// +public sealed record ResponsesFunctionCallOutputItem +{ + public string Type { get; init; } = "function_call_output"; + + public string CallId { get; init; } = string.Empty; + + public string Output { get; init; } = string.Empty; +} diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionTool.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionTool.cs new file mode 100644 index 00000000..fe9f5dc0 --- /dev/null +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesFunctionTool.cs @@ -0,0 +1,19 @@ +using System.Text.Json; + +namespace AIStudio.Provider.OpenAI; + +/// +/// The flat function tool definition shape expected by the OpenAI Responses API. +/// +public sealed record ResponsesFunctionTool +{ + public string Type { get; init; } = "function"; + + public string Name { get; init; } = string.Empty; + + public string Description { get; init; } = string.Empty; + + public JsonElement Parameters { get; init; } + + public bool Strict { get; init; } +} diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesResponse.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesResponse.cs new file mode 100644 index 00000000..69682e22 --- /dev/null +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesResponse.cs @@ -0,0 +1,62 @@ +using System.Text.Json; + +namespace AIStudio.Provider.OpenAI; + +/// +/// Non-streaming OpenAI Responses API result used during local tool execution. +/// +public sealed record ResponsesResponse +{ + public string Id { get; init; } = string.Empty; + + public string Model { get; init; } = string.Empty; + + public string? OutputText { get; init; } + + public IList Output { get; init; } = []; + + public IReadOnlyList GetFunctionCalls() => this.Output + .Where(x => ReadString(x, "type").Equals("function_call", StringComparison.Ordinal)) + .Select(x => new ResponsesFunctionCallItem + { + Type = ReadString(x, "type"), + CallId = ReadString(x, "call_id"), + Name = ReadString(x, "name"), + Arguments = ReadString(x, "arguments"), + }) + .Where(x => !string.IsNullOrWhiteSpace(x.CallId) && !string.IsNullOrWhiteSpace(x.Name)) + .ToList(); + + public string GetTextOutput() + { + if (!string.IsNullOrWhiteSpace(this.OutputText)) + return this.OutputText; + + return string.Concat(this.Output + .Where(x => ReadString(x, "type").Equals("message", StringComparison.Ordinal)) + .SelectMany(ReadContentItems) + .Where(x => ReadString(x, "type").Equals("output_text", StringComparison.Ordinal)) + .Select(x => ReadString(x, "text"))); + } + + private static IEnumerable ReadContentItems(JsonElement outputItem) + { + if (outputItem.ValueKind is not JsonValueKind.Object || + !outputItem.TryGetProperty("content", out var content) || + content.ValueKind is not JsonValueKind.Array) + yield break; + + foreach (var contentItem in content.EnumerateArray()) + yield return contentItem; + } + + private static string ReadString(JsonElement item, string propertyName) + { + if (item.ValueKind is not JsonValueKind.Object || + !item.TryGetProperty(propertyName, out var property) || + property.ValueKind is not JsonValueKind.String) + return string.Empty; + + return property.GetString() ?? string.Empty; + } +} diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs index 9b95162f..7c77ed1e 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs @@ -122,7 +122,8 @@ public sealed class ToolRegistry if (!isToolSelectionVisible) return []; - if (!modelCapabilities.Contains(Capability.CHAT_COMPLETION_API) || !modelCapabilities.Contains(Capability.FUNCTION_CALLING)) + if (!modelCapabilities.Contains(Capability.FUNCTION_CALLING) || + (!modelCapabilities.Contains(Capability.CHAT_COMPLETION_API) && !modelCapabilities.Contains(Capability.RESPONSES_API))) return []; var selectedToolIdSet = ToolSelectionRules.NormalizeSelection(selectedToolIds); diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index 26bb30b0..a434a0f9 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -28,6 +28,7 @@ - Fixed an issue where assistants hidden via configuration plugins still appear in "Send to ..." menus. Thanks, Gunnar, for reporting this issue. - Fixed an issue with voice recording where AI Studio could log errors and keep the feature available even though required parts failed to initialize. Voice recording is now disabled automatically for the current session in that case. - Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown. Thanks, Inga, for reporting this issue and providing some context on how to reproduce it. +- Fixed an issue where tools could not be used with OpenAI models that use the Responses API. - Fixed security issues in the native app runtime by strengthening how AI Studio creates and protects the secret values used for its internal secure connection. - Updated several security-sensitive Rust dependencies in the native runtime to address known vulnerabilities. - Updated .NET to v9.0.14 diff --git a/documentation/Tools.md b/documentation/Tools.md index 24269dfd..c589ce59 100644 --- a/documentation/Tools.md +++ b/documentation/Tools.md @@ -15,6 +15,40 @@ At startup, `ToolRegistry` reads all JSON definitions and matches each definitio The provider only sees tools that are available for the current component, selected by the user or defaults, supported by the model, configured correctly, and allowed by the provider confidence rules. +## Provider API Shapes + +The JSON definition in `wwwroot/tool_definitions` is the single source of truth for a tool. Do not create separate tool definition files for different provider APIs. Provider-specific request shapes are generated in code from the same `ToolDefinition`. + +Chat Completions compatible APIs use a nested function shape: + +```json +{ + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location.", + "parameters": {}, + "strict": true + } +} +``` + +The OpenAI Responses API uses a flat function shape: + +```json +{ + "type": "function", + "name": "get_current_weather", + "description": "Get the current weather in a given location.", + "parameters": {}, + "strict": true +} +``` + +Keep this difference contained in provider adapter code. `ProviderToolAdapters` maps a canonical `ToolDefinition` to the Chat Completions or Responses wire shape. Tool implementations should not know which provider API shape was used. + +Tool result handling also differs by API. Chat Completions returns tool calls in `message.tool_calls` and receives results as `role: "tool"` messages. Responses returns `function_call` output items and receives results as `function_call_output` input items correlated by `call_id`. Both paths still execute local tools through `ToolExecutor`, so validation, provider confidence checks, trace formatting, and blocked-call behavior stay shared. + ## Definition File Create one JSON file per tool under `wwwroot/tool_definitions`. The file describes the user-visible tool metadata, optional settings, and the function schema sent to the model. From f9032401bde13e6760d98d25643e2e5b5901f2a5 Mon Sep 17 00:00:00 2001 From: Nils Kruthoff Date: Tue, 19 May 2026 22:27:00 +0200 Subject: [PATCH 17/17] translation --- .../Assistants/I18N/allTexts.lua | 15 +++------------ .../plugin.lua | 15 +++------------ .../plugin.lua | 15 +++------------ 3 files changed, 9 insertions(+), 36 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index f277aca0..ce1be458 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -6004,6 +6004,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un -- no model selected UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected" +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::OPENAI::PROVIDEROPENAI::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details." + -- Model as configured by whisper.cpp UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp" @@ -6865,18 +6868,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T35170 -- Tool description UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description" --- Current Weather -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T2280588182"] = "Current Weather" - --- Demo Label -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T459484184"] = "Demo Label" - --- Required demo setting for validating tool settings in tests. It does not affect the weather result. -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T626664936"] = "Required demo setting for validating tool settings in tests. It does not affect the weather result." - --- 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T653336059"] = "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." - -- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user." diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index bae2e9aa..381931c0 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -6006,6 +6006,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un -- no model selected UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "Kein Modell ausgewählt" +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::OPENAI::PROVIDEROPENAI::T3117779001"] = "Die Anfrage zum Aufruf des Tools ist mit dem Statuscode {0} fehlgeschlagen. Details findest du in den Protokollen." + -- Model as configured by whisper.cpp UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Modell wie in whisper.cpp konfiguriert" @@ -6867,18 +6870,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T35170 -- Tool description UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Werkzeugbeschreibung" --- Current Weather -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T2280588182"] = "Aktuelles Wetter" - --- Demo Label -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T459484184"] = "Demo-Beschriftung" - --- Required demo setting for validating tool settings in tests. It does not affect the weather result. -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T626664936"] = "Erforderliche Demo-Einstellung zur Validierung der Werkzeug-Einstellungen in Tests. Sie beeinflusst das Wetterergebnis nicht." - --- 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T653336059"] = "Verwenden Sie dieses Demo-Tool, um das aktuelle Wetter für eine bestimmte Stadt und ein bestimmtes Bundesland abzurufen. Es dient in erster Linie dazu, Tool-Aufrufe und Tool-Einstellungen in AI Studio zu demonstrieren." - -- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Klar — ich kann eine einzelne Webseite laden, ihren Hauptinhalt extrahieren und daraus eine nutzbare, strukturierte Grundlage erstellen. Dem Nutzer gebe ich dann **eine natürlich formulierte Antwort**, nicht den rohen Extraktions-Output. Bitte sende mir einfach **die URL** der Seite, die ich verarbeiten soll. Wenn du möchtest, kannst du auch kurz dazuschreiben, **was genau ich daraus beantworten oder zusammenfassen soll**." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 474cff8a..4e749ece 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -6006,6 +6006,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un -- no model selected UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected" +-- The tool calling request failed with status code {0}. See the logs for details. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::OPENAI::PROVIDEROPENAI::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details." + -- Model as configured by whisper.cpp UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp" @@ -6867,18 +6870,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T35170 -- Tool description UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description" --- Current Weather -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T2280588182"] = "Current Weather" - --- Demo Label -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T459484184"] = "Demo Label" - --- Required demo setting for validating tool settings in tests. It does not affect the weather result. -UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T626664936"] = "Required demo setting for validating tool settings in tests. It does not affect the weather result." - --- 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::TOOLCALLINGIMPLEMENTATIONS::GETCURRENTWEATHERTOOL::T653336059"] = "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." - -- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user. UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user."