From 88eccb158cb2b23c5477c53af0b659e0000059aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peer=20Sch=C3=BCtt?=
<20603780+peerschuett@users.noreply.github.com>
Date: Thu, 23 Jul 2026 12:59:32 +0200
Subject: [PATCH] Hardening Tool Calling
---
.../Builder/LuaResponseParseError.cs | 2 +-
.../Assistants/I18N/allTexts.lua | 30 ++++++++
.../Chat/ContentBlockComponent.razor | 17 +++--
.../Chat/ContentBlockComponent.razor.cs | 1 +
app/MindWork AI Studio/Chat/ContentText.cs | 1 +
.../Components/JsonTreeView.razor | 26 +++++++
.../Components/JsonTreeView.razor.cs | 75 +++++++++++++++++++
.../plugin.lua | 34 ++++++++-
.../plugin.lua | 30 ++++++++
.../Provider/BaseProvider.cs | 30 +++++---
.../OpenAI/AssistantToolCallMessage.cs | 5 ++
.../OpenAI/ChatCompletionResponseMessage.cs | 2 +
.../Provider/OpenAI/ProviderOpenAI.cs | 27 ++++---
.../Provider/OpenAI/ResponsesResponse.cs | 30 ++++++--
app/MindWork AI Studio/Tools/ISource.cs | 4 +-
.../Tools/Services/GlobalShortcutService.cs | 8 +-
.../Tools/SourceExtensions.cs | 55 ++++++++++----
app/MindWork AI Studio/Tools/SourceOrigin.cs | 9 ++-
.../ToolCallingSystem/IToolImplementation.cs | 12 ++-
.../ToolCallingAvailability.cs | 26 -------
.../ToolCallingAvailabilityExtensions.cs | 29 +++++++
.../ReadWebPageTool.cs | 18 ++---
.../SearXNGWebSearchTool.cs | 48 +++++++-----
.../ToolCallingSystem/ToolExecutionModels.cs | 17 +----
.../Tools/ToolCallingSystem/ToolExecutor.cs | 14 ++--
.../ToolCallingSystem/ToolRuntimeStatus.cs | 19 +++++
.../ToolCallingSystem/ToolSelectionRules.cs | 13 ++++
.../Tools/WorkspaceBehaviour.cs | 6 +-
.../wwwroot/tool_definitions/web_search.json | 2 +-
documentation/Tools.md | 10 ++-
30 files changed, 449 insertions(+), 151 deletions(-)
create mode 100644 app/MindWork AI Studio/Components/JsonTreeView.razor
create mode 100644 app/MindWork AI Studio/Components/JsonTreeView.razor.cs
create mode 100644 app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingAvailabilityExtensions.cs
create mode 100644 app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRuntimeStatus.cs
diff --git a/app/MindWork AI Studio/Assistants/Builder/LuaResponseParseError.cs b/app/MindWork AI Studio/Assistants/Builder/LuaResponseParseError.cs
index 4b7ed309..b0f784ff 100644
--- a/app/MindWork AI Studio/Assistants/Builder/LuaResponseParseError.cs
+++ b/app/MindWork AI Studio/Assistants/Builder/LuaResponseParseError.cs
@@ -17,7 +17,7 @@ public enum LuaResponseParseError
public static class LuaResponseParseErrorExtension
{
- private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(LuaResponseParseErrorExtension).Namespace, nameof(LuaResponseParseErrorExtension));
+ private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(LuaResponseParseError).Namespace, nameof(LuaResponseParseError));
public static string GetMessage(this LuaResponseParseError parseError, string technicalDetails) => parseError switch
{
diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index 96c943db..29f7dcaf 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -9097,9 +9097,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T9397
-- The global shortcut could not be registered. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2266307101"] = "The global shortcut could not be registered. The previous shortcut remains active."
+-- Global shortcut
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2637055764"] = "Global shortcut"
+
-- The global shortcut change was cancelled. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T3299913860"] = "The global shortcut change was cancelled. The previous shortcut remains active."
+-- Toggle voice recording
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T40517664"] = "Toggle voice recording"
+
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "The configured transcription provider could not be created."
@@ -9217,12 +9223,24 @@ 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"
+-- Sources used by tools
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T535360212"] = "Sources used by tools"
+
-- Tool
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Tool"
-- Tool description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description"
+-- Please select an LLM provider.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1110311702"] = "Please select an LLM provider."
+
+-- The selected model does not support tool calling.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1960397623"] = "The selected model does not support tool calling."
+
+-- Tool calling for this provider is not implemented yet.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T3776963202"] = "Tool calling for this provider is not implemented yet."
+
-- (Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1410249500"] = "(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
@@ -9358,6 +9376,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS:
-- SearXNG URL
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG URL"
+-- Using tools: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T2834986024"] = "Using tools: {0}"
+
+-- Using tool: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T4185351801"] = "Using tool: {0}"
+
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation"
@@ -9499,11 +9523,17 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T497939286"] =
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Please select a model."
+-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
+
-- Unnamed workspace
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace"
-- Delete Chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat"
+-- Are you sure you want to delete the temporary chat '{0}'?
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?"
+
-- Unnamed chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3310482275"] = "Unnamed chat"
diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor
index cd3bf46a..8138a0ff 100644
--- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor
+++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor
@@ -148,11 +148,6 @@
@invocation.StatusMessage
}
- @T("Result")
-
- @this.GetToolInvocationResult(invocation)
-
-
@T("Arguments")
@if (invocation.Arguments.Count == 0)
{
@@ -169,6 +164,18 @@
}
}
+
+ @T("Result")
+
+ @if (invocation.JsonResult is not null)
+ {
+
+ }
+ else
+ {
+ @this.GetToolInvocationResult(invocation)
+ }
+
}
}
diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs
index 32ee1cd9..d9bafdc4 100644
--- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs
+++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs
@@ -217,6 +217,7 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
hash.Add(invocation.Status);
hash.Add(invocation.StatusMessage);
hash.Add(invocation.Result);
+ hash.Add(invocation.JsonResult is not null);
hash.Add(invocation.Arguments.Count);
foreach (var argument in invocation.Arguments)
{
diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs
index eafea313..20420fe6 100644
--- a/app/MindWork AI Studio/Chat/ContentText.cs
+++ b/app/MindWork AI Studio/Chat/ContentText.cs
@@ -266,6 +266,7 @@ public sealed class ContentText : IContent
StatusMessage = x.StatusMessage,
Arguments = new Dictionary(x.Arguments, StringComparer.Ordinal),
Result = x.Result,
+ JsonResult = x.JsonResult?.DeepClone(),
})],
};
diff --git a/app/MindWork AI Studio/Components/JsonTreeView.razor b/app/MindWork AI Studio/Components/JsonTreeView.razor
new file mode 100644
index 00000000..c2526674
--- /dev/null
+++ b/app/MindWork AI Studio/Components/JsonTreeView.razor
@@ -0,0 +1,26 @@
+@using MudBlazor
+
+
+
+ @if (item.Value is JsonTreeNode node)
+ {
+
+
+
+ @node.Text
+
+
+
+ }
+
+
diff --git a/app/MindWork AI Studio/Components/JsonTreeView.razor.cs b/app/MindWork AI Studio/Components/JsonTreeView.razor.cs
new file mode 100644
index 00000000..2dbf056b
--- /dev/null
+++ b/app/MindWork AI Studio/Components/JsonTreeView.razor.cs
@@ -0,0 +1,75 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+using Microsoft.AspNetCore.Components;
+
+using MudBlazor;
+
+namespace AIStudio.Components;
+
+public partial class JsonTreeView : ComponentBase
+{
+ [Parameter]
+ public JsonNode? Value { get; set; }
+
+ private IReadOnlyCollection> items = [];
+
+ protected override void OnParametersSet()
+ {
+ this.items = [CreateTreeItem("$", this.Value)];
+ }
+
+ private static TreeItemData CreateTreeItem(string label, JsonNode? value)
+ {
+ var children = CreateChildren(value);
+ return new TreeItemData
+ {
+ Expanded = false,
+ Expandable = children.Count > 0,
+ Value = new JsonTreeNode
+ {
+ Text = $"{label}: {FormatValue(value)}",
+ Icon = GetIcon(value),
+ Expandable = children.Count > 0,
+ },
+ Children = children,
+ };
+ }
+
+ private static List> CreateChildren(JsonNode? value) => value switch
+ {
+ JsonObject jsonObject => jsonObject
+ .Select(property => CreateTreeItem(JsonSerializer.Serialize(property.Key), property.Value))
+ .ToList(),
+ JsonArray jsonArray => jsonArray
+ .Select((item, index) => CreateTreeItem($"[{index}]", item))
+ .ToList(),
+ _ => [],
+ };
+
+ private static string FormatValue(JsonNode? value) => value switch
+ {
+ JsonObject jsonObject when jsonObject.Count == 0 => "{}",
+ JsonObject => "{...}",
+ JsonArray jsonArray when jsonArray.Count == 0 => "[]",
+ JsonArray => "[...]",
+ null => "null",
+ _ => value.ToJsonString(),
+ };
+
+ private static string GetIcon(JsonNode? value) => value switch
+ {
+ JsonObject => Icons.Material.Filled.DataObject,
+ JsonArray => Icons.Material.Filled.DataArray,
+ _ => Icons.Material.Filled.Code,
+ };
+
+ private sealed class JsonTreeNode
+ {
+ public string Text { get; init; } = string.Empty;
+
+ public string Icon { get; init; } = string.Empty;
+
+ public bool Expandable { get; init; }
+ }
+}
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 29b48f23..b8302e91 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
@@ -3619,7 +3619,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3728248397
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Werkzeugeinstellungen"
-- This tool has been disabled by your organization.
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3794167684"] = "Dieses Tool wurde von Ihrer Organisation deaktiviert."
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3794167684"] = "Dieses Werkzeug wurde von Ihrer Organisation deaktiviert."
-- Status
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T6222351"] = "Status"
@@ -3739,7 +3739,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3119156561"] = "Erforderl
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Schließen"
-- This tool has been disabled by your organization.
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3794167684"] = "Dieses Tool wurde von Ihrer Organisation deaktiviert."
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3794167684"] = "Dieses Werkzeug wurde von Ihrer Organisation deaktiviert."
-- No tools are available in this context.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "Keine Werkzeuge sind in diesem Kontext verfügbar."
@@ -9099,9 +9099,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T9397
-- The global shortcut could not be registered. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2266307101"] = "Die globale Tastenkombination konnte nicht registriert werden. Die vorherige Tastenkombination bleibt aktiv."
+-- Global shortcut
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2637055764"] = "Globale Tastenkombination"
+
-- The global shortcut change was cancelled. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T3299913860"] = "Die Änderung der globalen Tastenkombination wurde abgebrochen. Die vorherige Tastenkombination bleibt aktiv."
+-- Toggle voice recording
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T40517664"] = "Sprachaufnahme umschalten"
+
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "Der konfigurierte Transkriptionsanbieter konnte nicht erstellt werden."
@@ -9219,12 +9225,24 @@ 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"
+-- Sources used by tools
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T535360212"] = "Quellen, die von Werkzeugen verwendet werden"
+
-- Tool
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Werkzeug"
-- Tool description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Werkzeugbeschreibung"
+-- Please select an LLM provider.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1110311702"] = "Bitte wählen Sie einen LLM-Anbieter aus."
+
+-- The selected model does not support tool calling.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1960397623"] = "Das ausgewählte Modell unterstützt keine Werkzeug-Nutzung."
+
+-- Tool calling for this provider is not implemented yet.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T3776963202"] = "Werkzeug-Aufruf für diesen Anbieter ist noch nicht implementiert."
+
-- (Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1410249500"] = "(Optional) Host-Whitelist für private oder VPN-Webseiten. Aus Sicherheitsgründen dürfen private oder VPN-Webseiten standardmäßig nicht gelesen werden. Trennen Sie Host-Muster durch Kommas, z. B. example.de, *.example.de. Erlaubte private Hosts erfordern einen Anbieter mit hoher Zuverlässigkeit. Für erlaubte interne Hosts versucht AI Studio außerdem automatisch die Standard-Anmeldung des Betriebssystems, wenn der Server mit integrierter Authentifizierung antwortet."
@@ -9360,6 +9378,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS:
-- SearXNG URL
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG-URL"
+-- Using tools: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T2834986024"] = "Verwendung von Tools: {0}"
+
+-- Using tool: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T4185351801"] = "Verwendetes Tool: {0}"
+
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc-Installation"
@@ -9501,11 +9525,17 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T497939286"] =
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Bitte wählen Sie ein Modell aus."
+-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1016188706"] = "Möchtest du den Chat '{0}' im Arbeitsbereich '{1}' wirklich löschen?"
+
-- Unnamed workspace
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unbenannter Arbeitsbereich"
-- Delete Chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Chat löschen"
+-- Are you sure you want to delete the temporary chat '{0}'?
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3043761007"] = "Möchtest du den temporären Chat '{0}' wirklich löschen?"
+
-- Unnamed chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3310482275"] = "Unbenannter Chat"
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 acd26b0a..816c7c58 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
@@ -9099,9 +9099,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T9397
-- The global shortcut could not be registered. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2266307101"] = "The global shortcut could not be registered. The previous shortcut remains active."
+-- Global shortcut
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T2637055764"] = "Global shortcut"
+
-- The global shortcut change was cancelled. The previous shortcut remains active.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T3299913860"] = "The global shortcut change was cancelled. The previous shortcut remains active."
+-- Toggle voice recording
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::GLOBALSHORTCUTSERVICE::T40517664"] = "Toggle voice recording"
+
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "The configured transcription provider could not be created."
@@ -9219,12 +9225,24 @@ 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"
+-- Sources used by tools
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T535360212"] = "Sources used by tools"
+
-- Tool
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Tool"
-- Tool description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description"
+-- Please select an LLM provider.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1110311702"] = "Please select an LLM provider."
+
+-- The selected model does not support tool calling.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T1960397623"] = "The selected model does not support tool calling."
+
+-- Tool calling for this provider is not implemented yet.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T3776963202"] = "Tool calling for this provider is not implemented yet."
+
-- (Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1410249500"] = "(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
@@ -9360,6 +9378,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS:
-- SearXNG URL
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG URL"
+-- Using tools: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T2834986024"] = "Using tools: {0}"
+
+-- Using tool: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLRUNTIMESTATUS::T4185351801"] = "Using tool: {0}"
+
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation"
@@ -9501,11 +9525,17 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T497939286"] =
-- Please select a model.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Please select a model."
+-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
+
-- Unnamed workspace
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace"
-- Delete Chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat"
+-- Are you sure you want to delete the temporary chat '{0}'?
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?"
+
-- Unnamed chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T3310482275"] = "Unnamed chat"
diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs
index 56bb80b2..32d4d68e 100644
--- a/app/MindWork AI Studio/Provider/BaseProvider.cs
+++ b/app/MindWork AI Studio/Provider/BaseProvider.cs
@@ -1034,13 +1034,16 @@ public abstract class BaseProvider : IProvider, ISecretId
var internalMessages = new List();
var toolCallCount = 0;
+ var toolResultCharacterCount = 0L;
+ var toolSources = new List();
while (true)
{
- var finalResponseRequired = toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS;
+ var finalResponseInstruction = ToolSelectionRules.GetToolCallsUnavailableInstruction(toolCallCount, toolResultCharacterCount);
+ var finalResponseRequired = finalResponseInstruction is not null;
var requestSystemPrompt = finalResponseRequired
? systemPrompt with
{
- Content = $"{systemPrompt.Content}{Environment.NewLine}{Environment.NewLine}{ToolSelectionRules.GetMaxToolCallsFinalResponseInstruction()}",
+ Content = $"{systemPrompt.Content}{Environment.NewLine}{Environment.NewLine}{finalResponseInstruction}",
}
: systemPrompt;
ChatCompletionAPIRequest requestDtoBase = await requestFactory(
@@ -1051,6 +1054,10 @@ public abstract class BaseProvider : IProvider, ISecretId
{
Messages = [..requestDtoBase.Messages, ..internalMessages],
Stream = false,
+ ParallelToolCalls = requestDtoBase.Tools is not null &&
+ chatModel.Id.Contains("gpt-oss", StringComparison.InvariantCultureIgnoreCase)
+ ? false
+ : requestDtoBase.ParallelToolCalls,
};
var response = await this.ExecuteChatCompletionRequest(requestDto, requestPath, requestedSecret, headersAction, token);
var responseMessage = response?.Choices.FirstOrDefault()?.Message;
@@ -1065,9 +1072,9 @@ public abstract class BaseProvider : IProvider, ISecretId
{
await ResetToolRuntimeStatusAsync();
if (!string.IsNullOrWhiteSpace(responseMessage.Content))
- yield return new ContentStreamChunk(responseMessage.Content, []);
+ yield return new ContentStreamChunk(responseMessage.Content, [..toolSources]);
else if (toolCallCount > 0)
- yield return new ContentStreamChunk("The model completed the tool call but did not return a final answer.", []);
+ yield return new ContentStreamChunk("The model completed the tool call but did not return a final answer.", [..toolSources]);
yield break;
}
@@ -1076,9 +1083,9 @@ public abstract class BaseProvider : IProvider, ISecretId
{
await ResetToolRuntimeStatusAsync();
if (!string.IsNullOrWhiteSpace(responseMessage.Content))
- yield return new ContentStreamChunk(responseMessage.Content, []);
+ yield return new ContentStreamChunk(responseMessage.Content, [..toolSources]);
else
- yield return new ContentStreamChunk("The model did not return a final answer after completing the available tool calls.", []);
+ yield return new ContentStreamChunk("The model did not return a final answer after completing the available tool calls.", [..toolSources]);
yield break;
}
@@ -1091,24 +1098,25 @@ public abstract class BaseProvider : IProvider, ISecretId
internalMessages.Add(new AssistantToolCallMessage
{
Content = responseMessage.Content,
+ ReasoningContent = responseMessage.ReasoningContent,
ToolCalls = toolCalls,
});
foreach (var toolCall in toolCalls)
{
- if (toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS)
+ var toolCallsUnavailableInstruction = ToolSelectionRules.GetToolCallsUnavailableInstruction(toolCallCount, toolResultCharacterCount);
+ if (toolCallsUnavailableInstruction is not null)
{
- var finalResponseInstruction = ToolSelectionRules.GetMaxToolCallsFinalResponseInstruction();
internalMessages.Add(new ToolResultMessage
{
- Content = finalResponseInstruction,
+ Content = toolCallsUnavailableInstruction,
ToolCallId = toolCall.Id,
});
continue;
}
toolCallCount++;
- var (toolContent, trace, requiredProviderConfidence) = await toolExecutor.ExecuteAsync(
+ var (toolContent, trace, requiredProviderConfidence, sources) = await toolExecutor.ExecuteAsync(
toolCall.Id,
toolCall.Function.Name,
toolCall.Function.Arguments,
@@ -1116,8 +1124,10 @@ public abstract class BaseProvider : IProvider, ISecretId
this.Provider.GetConfidence(settingsManager).Level,
toolCallCount,
token);
+ toolResultCharacterCount += toolContent.Length;
chatThread.RequireProviderConfidence(requiredProviderConfidence);
+ toolSources.MergeSources(sources);
currentAssistantContent?.ToolInvocations.Add(trace);
internalMessages.Add(new ToolResultMessage
{
diff --git a/app/MindWork AI Studio/Provider/OpenAI/AssistantToolCallMessage.cs b/app/MindWork AI Studio/Provider/OpenAI/AssistantToolCallMessage.cs
index 340d1e74..693b40bd 100644
--- a/app/MindWork AI Studio/Provider/OpenAI/AssistantToolCallMessage.cs
+++ b/app/MindWork AI Studio/Provider/OpenAI/AssistantToolCallMessage.cs
@@ -1,3 +1,5 @@
+using System.Text.Json.Serialization;
+
namespace AIStudio.Provider.OpenAI;
public sealed record AssistantToolCallMessage : IMessageBase
@@ -6,5 +8,8 @@ public sealed record AssistantToolCallMessage : IMessageBase
public string? Content { get; init; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? ReasoningContent { get; init; }
+
public IList ToolCalls { get; init; } = [];
}
diff --git a/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionResponseMessage.cs b/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionResponseMessage.cs
index cb8ff196..205194e4 100644
--- a/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionResponseMessage.cs
+++ b/app/MindWork AI Studio/Provider/OpenAI/ChatCompletionResponseMessage.cs
@@ -6,5 +6,7 @@ public sealed record ChatCompletionResponseMessage
public string? Content { get; init; }
+ public string? ReasoningContent { get; init; }
+
public IList? ToolCalls { get; init; }
}
diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs
index 8dd3b24f..1cba21d4 100644
--- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs
+++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs
@@ -335,16 +335,19 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
// reasoning items emitted alongside function calls.
var internalItems = new List