From 57d60a7b85e7189970b30b85b01c22705bbf3535 Mon Sep 17 00:00:00 2001 From: hart_s3 Date: Tue, 28 Apr 2026 14:04:10 +0200 Subject: [PATCH] Add configurable timeout for LLM provider requests --- .../Plugins/configuration/plugin.lua | 4 ++ .../Provider/BaseProvider.cs | 37 +++++++++++++++++-- .../Settings/DataModel/DataApp.cs | 5 +++ .../Tools/PluginSystem/PluginConfiguration.cs | 3 ++ .../PluginSystem/PluginFactory.Loading.cs | 4 ++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 6cd5858d..af7c6ae0 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -212,6 +212,10 @@ CONFIG["SETTINGS"] = {} -- Examples are: "CmdOrControl+Shift+D", "Alt+F9", "F8" -- CONFIG["SETTINGS"]["DataApp.ShortcutVoiceRecording"] = "CmdOrControl+1" +-- Configure the HTTP timeout for requests to LLM providers, in seconds. +-- The default is 3600 (1 hour). +-- CONFIG["SETTINGS"]["DataApp.ProviderHttpTimeoutSeconds"] = 3600 + -- Example chat templates for this configuration: CONFIG["CHAT_TEMPLATES"] = {} diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 23857ac8..ebb0feda 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -24,9 +24,10 @@ namespace AIStudio.Provider; /// public abstract class BaseProvider : IProvider, ISecretId { - private static readonly TimeSpan HTTP_TIMEOUT = TimeSpan.FromHours(1); + private const int DEFAULT_HTTP_TIMEOUT_SECONDS = 3600; private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(BaseProvider).Namespace, nameof(BaseProvider)); + private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); /// /// The HTTP client to use it for all requests. @@ -76,7 +77,7 @@ public abstract class BaseProvider : IProvider, ISecretId // Set the base URL: this.HttpClient.BaseAddress = new(url); - this.HttpClient.Timeout = HTTP_TIMEOUT; + this.HttpClient.Timeout = GetProviderHttpTimeout(); } #region Handling of IProvider, which all providers must implement @@ -156,11 +157,41 @@ public abstract class BaseProvider : IProvider, ISecretId protected Task SendTimeoutError(string action) => MessageBus.INSTANCE.SendError(new( Icons.Material.Filled.HourglassTop, string.Format( - TB("The request to the LLM provider '{0}' (type={1}) timed out after 1 hour while {2}. Please try again or check whether the provider is still responding."), + TB("The request to the LLM provider '{0}' (type={1}) timed out after {2} while {3}. Please try again or check whether the provider is still responding."), this.InstanceName, this.Provider, + GetProviderHttpTimeoutDescription(), action))); + private static TimeSpan GetProviderHttpTimeout() + { + var seconds = SETTINGS_MANAGER.ConfigurationData.App.ProviderHttpTimeoutSeconds; + if (seconds <= 0) + seconds = DEFAULT_HTTP_TIMEOUT_SECONDS; + + return TimeSpan.FromSeconds(seconds); + } + + private static string GetProviderHttpTimeoutDescription() + { + var timeout = GetProviderHttpTimeout(); + + if (timeout.TotalHours >= 1 && timeout.TotalMinutes % 60 == 0) + { + var hours = (int)timeout.TotalHours; + return hours == 1 ? "1 hour" : $"{hours} hours"; + } + + if (timeout.TotalMinutes >= 1 && timeout.TotalSeconds % 60 == 0) + { + var minutes = (int)timeout.TotalMinutes; + return minutes == 1 ? "1 minute" : $"{minutes} minutes"; + } + + var seconds = (int)timeout.TotalSeconds; + return seconds == 1 ? "1 second" : $"{seconds} seconds"; + } + protected async Task GetModelLoadingSecretKey(SecretStoreType storeType, string? apiKeyProvisional = null, bool isTryingSecret = false) => apiKeyProvisional switch { not null => apiKeyProvisional, diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs index 3a62164b..1b25ba88 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs @@ -94,6 +94,11 @@ public sealed class DataApp(Expression>? configSelection = n /// public string ShortcutVoiceRecording { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShortcutVoiceRecording, string.Empty); + /// + /// The HTTP timeout in seconds for requests to LLM providers. + /// + public int ProviderHttpTimeoutSeconds { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ProviderHttpTimeoutSeconds, 3600); + /// /// Should the user be allowed to add providers? /// diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index da504b29..35b2a114 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -140,6 +140,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: timeout for HTTP requests to providers + ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ProviderHttpTimeoutSeconds, 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 aedc7f7e..561ae362 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 if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.ShortcutVoiceRecording, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + // Check for the provider HTTP timeout: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.ProviderHttpTimeoutSeconds, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + // Check if audit is required before it can be activated if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AssistantPluginAudit, x => x.RequireAuditBeforeActivation, AVAILABLE_PLUGINS)) wasConfigurationChanged = true;