From 1c0c2a38550d6c3f0612898e07de986c40f8679d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:43:06 +0200 Subject: [PATCH] Merge Error Fixes --- .../Assistants/AssistantBase.razor | 2 +- .../Components/ChatComponent.razor | 2 +- .../Components/ChatComponent.razor.cs | 13 ++- .../Provider/BaseProvider.cs | 21 +++-- .../Provider/OpenAI/ProviderOpenAI.cs | 89 ++++++++----------- .../Provider/OpenAI/ResponsesAPIRequest.cs | 2 - .../Tools/SecretStoreType.cs | 7 +- .../Tools/SecretStoreTypeExtensions.cs | 3 +- .../ToolCallingSystem/ToolSettingsSecretId.cs | 2 +- .../ToolCallingSystem/ToolSettingsService.cs | 6 +- 10 files changed, 69 insertions(+), 78 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor b/app/MindWork AI Studio/Assistants/AssistantBase.razor index a65a519e..9b11152c 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor @@ -165,7 +165,7 @@ @if (this.SettingsManager.IsToolSelectionVisible(this.Component)) { - + } diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor index 80437388..9fa82897 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor +++ b/app/MindWork AI Studio/Components/ChatComponent.razor @@ -124,7 +124,7 @@ - + @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) { diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 01034107..62edb472 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -78,6 +78,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private Guid loadedParameterWorkspaceId = Guid.Empty; private Guid foregroundChatId = Guid.Empty; private int workspaceHeaderSyncVersion; + private CancellationTokenSource? cancellationTokenSource; // Unfortunately, we need the input field reference to blur the focus away. Without // this, we cannot clear the input field. @@ -702,7 +703,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable // ProviderSettings = this.Provider, // IsForeground = true, //}); - using (this.cancellationTokenSource = new()) + using (this.cancellationTokenSource = new CancellationTokenSource()) { this.StateHasChanged(); this.ChatThread!.RuntimeComponent = Tools.Components.CHAT; @@ -719,12 +720,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable // Save the chat: if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) { - ChatThread = this.ChatThread!, - AIText = aiText, - LastUserPrompt = lastUserPrompt, - ProviderSettings = this.Provider, - IsForeground = true, - }); + await this.SaveThread(); + } await this.SyncForegroundChatAsync(); this.StateHasChanged(); @@ -1133,4 +1130,4 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable } #endregion -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 801c9cdc..79776dc9 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -957,7 +957,6 @@ public abstract class BaseProvider : IProvider, ISecretId /// The selected chat model. /// The current chat thread. /// The settings manager. - /// Builds the provider-specific base messages. /// Builds the provider-specific request body. /// The secret store type. /// Whether the API key is optional. @@ -969,19 +968,19 @@ public abstract class BaseProvider : IProvider, ISecretId /// The delta stream line type. /// The annotation stream line type. /// The streamed content chunks. - protected async IAsyncEnumerable StreamOpenAICompatibleChatCompletion( + protected async IAsyncEnumerable StreamOpenAICompatibleChatCompletion( string providerName, Model chatModel, ChatThread chatThread, SettingsManager settingsManager, - Func>> messagesFactory, - Func, Task> requestFactory, + Func, IList?, Task> requestFactory, SecretStoreType storeType = SecretStoreType.LLM_PROVIDER, bool isTryingSecret = false, string systemPromptRole = "system", string requestPath = "chat/completions", Action? headersAction = null, [EnumeratorCancellation] CancellationToken token = default) + where TRequest : ChatCompletionAPIRequest where TDelta : IResponseStreamLine where TAnnotation : IAnnotationStreamLine { @@ -1000,7 +999,6 @@ public abstract class BaseProvider : IProvider, ISecretId // Parse the API parameters: var apiParameters = this.ParseAdditionalApiParameters(); - var baseMessages = await messagesFactory(); 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; @@ -1023,7 +1021,12 @@ public abstract class BaseProvider : IProvider, ISecretId var toolCallCount = 0; while (true) { - var requestDto = await requestFactory(systemPrompt, [..baseMessages, ..internalMessages], apiParameters, false, providerTools); + ChatCompletionAPIRequest requestDtoBase = await requestFactory(systemPrompt, apiParameters, providerTools); + var requestDto = requestDtoBase with + { + Messages = [..requestDtoBase.Messages, ..internalMessages], + Stream = false, + }; var response = await this.ExecuteChatCompletionRequest(requestDto, requestPath, requestedSecret, headersAction, token); var responseMessage = response?.Choices.FirstOrDefault()?.Message; if (responseMessage is null) @@ -1106,7 +1109,7 @@ public abstract class BaseProvider : IProvider, ISecretId } // Prepare the provider HTTP chat request: - var providerChatRequest = JsonSerializer.Serialize(await requestFactory(systemPrompt, baseMessages, apiParameters, true, null), JSON_SERIALIZER_OPTIONS); + var providerChatRequest = JsonSerializer.Serialize(await requestFactory(systemPrompt, apiParameters, null), JSON_SERIALIZER_OPTIONS); async Task RequestBuilder() { @@ -1143,7 +1146,7 @@ public abstract class BaseProvider : IProvider, ISecretId headersAction?.Invoke(request.Headers); request.Content = new StringContent(JsonSerializer.Serialize(requestDto, JSON_SERIALIZER_OPTIONS), Encoding.UTF8, "application/json"); - using var response = await this.httpClient.SendAsync(request, token); + using var response = await this.HttpClient.SendAsync(request, token); if (!response.IsSuccessStatusCode) { var responseBody = await response.Content.ReadAsStringAsync(token); @@ -1477,4 +1480,4 @@ public abstract class BaseProvider : IProvider, ISecretId _ => string.Empty, }; -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index 284b484f..242e8ec1 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -12,7 +12,6 @@ using AIStudio.Tools.ToolCallingSystem; using AIStudio.Tools.Services; using Microsoft.Extensions.DependencyInjection; -using AIStudio.Tools.PluginSystem; namespace AIStudio.Provider.OpenAI; @@ -24,8 +23,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderOpenAI).Namespace, nameof(ProviderOpenAI)); - private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderOpenAI).Namespace, nameof(ProviderOpenAI)); - #region Implementation of IProvider /// @@ -121,44 +118,48 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur if (!usingResponsesAPI) { - await foreach (var content in this.StreamOpenAICompatibleChatCompletion( + await foreach (var content in this.StreamOpenAICompatibleChatCompletion( "OpenAI", chatModel, chatThread, settingsManager, - () => chatThread.Blocks.BuildMessagesAsync( - this.Provider, - chatModel, - role => role switch - { - ChatRole.USER => "user", - ChatRole.AI => "assistant", - ChatRole.AGENT => "assistant", - ChatRole.SYSTEM => systemPromptRole, - _ => "user", - }, - text => new SubContentText - { - Text = text, - }, - async attachment => new SubContentImageUrlNested - { - ImageUrl = new SubContentImageUrlData - { - Url = await attachment.TryAsBase64(token: token) is (true, var base64Content) - ? $"data:{attachment.DetermineMimeType()};base64,{base64Content}" - : string.Empty, - }, - }), - (systemPrompt, messages, apiParameters, stream, tools) => Task.FromResult(new ChatCompletionAPIRequest + async (systemPrompt, apiParameters, tools) => { - Model = chatModel.Id, - Messages = [systemPrompt, ..messages], - Stream = stream, - Tools = tools, - ParallelToolCalls = tools is null ? null : true, - AdditionalApiParameters = apiParameters, - }), + var messages = await chatThread.Blocks.BuildMessagesAsync( + this.Provider, + chatModel, + role => role switch + { + ChatRole.USER => "user", + ChatRole.AI => "assistant", + ChatRole.AGENT => "assistant", + ChatRole.SYSTEM => systemPromptRole, + _ => "user", + }, + text => new SubContentText + { + Text = text, + }, + async attachment => new SubContentImageUrlNested + { + ImageUrl = new SubContentImageUrlData + { + Url = await attachment.TryAsBase64(token: token) is (true, var base64Content) + ? $"data:{attachment.DetermineMimeType()};base64,{base64Content}" + : string.Empty, + }, + }); + + return new ChatCompletionAPIRequest + { + Model = chatModel.Id, + Messages = [systemPrompt, ..messages], + Stream = true, + Tools = tools, + ParallelToolCalls = tools is null ? null : true, + AdditionalApiParameters = apiParameters, + }; + }, systemPromptRole: systemPromptRole, requestPath: "chat/completions", token: token)) @@ -174,19 +175,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur Content = chatThread.PrepareSystemPrompt(settingsManager), }; - // - // Prepare the tools we want to use: - // - IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) switch - { - true => [ ProviderTools.WEB_SEARCH ], - _ => [] - }; - - - // Parse the API parameters: - var apiParameters = this.ParseAdditionalApiParameters("input", "store", "tools"); - // Build the list of messages: var messages = await chatThread.Blocks.BuildMessagesAsync( this.Provider, chatModel, @@ -279,7 +267,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur Store = false, // Tools we want to use: - ProviderTools = providerTools, Tools = providerTools, // Additional API parameters: @@ -438,7 +425,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur 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); + using var response = await this.HttpClient.SendAsync(request, token); if (!response.IsSuccessStatusCode) { var responseBody = await response.Content.ReadAsStringAsync(token); diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs index 8730bbe8..148edc79 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs @@ -10,13 +10,11 @@ namespace AIStudio.Provider.OpenAI; /// Whether to stream the response. /// Whether to store the response on the server (usually OpenAI's infrastructure). /// The provider-side tools and local function tools to use for the request. -/// The provider-side tools to use for the request. public record ResponsesAPIRequest( string Model, 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/Tools/SecretStoreType.cs b/app/MindWork AI Studio/Tools/SecretStoreType.cs index 5e9182d7..74f310d1 100644 --- a/app/MindWork AI Studio/Tools/SecretStoreType.cs +++ b/app/MindWork AI Studio/Tools/SecretStoreType.cs @@ -34,4 +34,9 @@ public enum SecretStoreType /// Data source secrets. Uses the "data-source::" prefix. /// DATA_SOURCE, -} \ No newline at end of file + + /// + /// Tool setting secrets. Uses the "tool::" prefix. + /// + TOOL_SETTINGS, +} diff --git a/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs b/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs index 5e8ae2f0..f1e90d81 100644 --- a/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs +++ b/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs @@ -17,7 +17,8 @@ public static class SecretStoreTypeExtensions SecretStoreType.TRANSCRIPTION_PROVIDER => "transcription", SecretStoreType.IMAGE_PROVIDER => "image", SecretStoreType.DATA_SOURCE => "data-source", + SecretStoreType.TOOL_SETTINGS => "tool", _ => "provider", }; -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsSecretId.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsSecretId.cs index 25b3c687..003934e6 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsSecretId.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsSecretId.cs @@ -4,7 +4,7 @@ namespace AIStudio.Tools.ToolCallingSystem; internal sealed record ToolSettingsSecretId(string ToolId, string FieldName) : ISecretId { - public string SecretId => $"tool::{this.ToolId}"; + public string SecretId => this.ToolId; public string SecretName => this.FieldName; } diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs index a1142913..94c0da96 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs @@ -23,7 +23,7 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer if (fieldDefinition.Secret) { - var response = await rustService.GetSecret(new ToolSettingsSecretId(definition.Id, fieldName), isTrying: true); + var response = await rustService.GetSecret(new ToolSettingsSecretId(definition.Id, fieldName), SecretStoreType.TOOL_SETTINGS, isTrying: true); if (response.Success) values[fieldName] = await response.Secret.Decrypt(Program.ENCRYPTION); @@ -99,9 +99,9 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer { var secretId = new ToolSettingsSecretId(definition.Id, fieldName); if (string.IsNullOrWhiteSpace(value)) - await rustService.DeleteSecret(secretId); + await rustService.DeleteSecret(secretId, SecretStoreType.TOOL_SETTINGS); else - await rustService.SetSecret(secretId, value); + await rustService.SetSecret(secretId, value, SecretStoreType.TOOL_SETTINGS); continue; }