From 25c4d5f185c836d4084d2c5c773a4951ae238039 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:26:52 +0200 Subject: [PATCH] feat: LM Studio response API and Ollama OpenAI API --- CMakeLists.txt | 2 + ChatView/ClientInterface.cpp | 6 +- LLMClientInterface.cpp | 5 +- QuickRefactorHandler.cpp | 7 +- pluginllmcore/Provider.cpp | 30 ----- pluginllmcore/Provider.hpp | 2 - providers/LMStudioProvider.cpp | 2 +- providers/LMStudioResponsesProvider.cpp | 148 ++++++++++++++++++++++++ providers/LMStudioResponsesProvider.hpp | 40 +++++++ providers/OllamaCompatProvider.cpp | 124 ++++++++++++++++++++ providers/OllamaCompatProvider.hpp | 40 +++++++ providers/OllamaProvider.cpp | 2 +- providers/OpenAIProvider.cpp | 2 +- providers/OpenAIResponsesProvider.cpp | 23 +--- providers/Providers.hpp | 4 + settings/CMakeLists.txt | 1 + settings/ConfigurationManager.cpp | 5 +- settings/GeneralSettings.cpp | 23 +++- settings/ProviderNameMigration.hpp | 29 +++++ 19 files changed, 423 insertions(+), 72 deletions(-) create mode 100644 providers/LMStudioResponsesProvider.cpp create mode 100644 providers/LMStudioResponsesProvider.hpp create mode 100644 providers/OllamaCompatProvider.cpp create mode 100644 providers/OllamaCompatProvider.hpp create mode 100644 settings/ProviderNameMigration.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d052ed..7117dc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,10 +96,12 @@ add_qtc_plugin(QodeAssist templates/OpenAIResponses.hpp providers/Providers.hpp providers/OllamaProvider.hpp providers/OllamaProvider.cpp + providers/OllamaCompatProvider.hpp providers/OllamaCompatProvider.cpp providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp providers/MistralAIProvider.hpp providers/MistralAIProvider.cpp providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp + providers/LMStudioResponsesProvider.hpp providers/LMStudioResponsesProvider.cpp providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp diff --git a/ChatView/ClientInterface.cpp b/ChatView/ClientInterface.cpp index 28693bd..6a14632 100644 --- a/ChatView/ClientInterface.cpp +++ b/ChatView/ClientInterface.cpp @@ -452,10 +452,8 @@ void ClientInterface::handleRequestFailed(const QString &requestId, const QStrin if (it == m_activeRequests.end()) return; - QString enriched = it->provider ? it->provider->enrichErrorMessage(error) : error; - - LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, enriched)); - emit errorOccurred(enriched); + LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, error)); + emit errorOccurred(error); m_activeRequests.erase(it); m_accumulatedResponses.remove(requestId); diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index 1ce080b..72f1813 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -70,9 +70,8 @@ void LLMClientInterface::handleRequestFailed(const QString &requestId, const QSt return; const RequestContext &ctx = it.value(); - QString enriched = ctx.provider ? ctx.provider->enrichErrorMessage(error) : error; - LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, enriched)); + LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, error)); // Send LSP error response to client QJsonObject response; @@ -81,7 +80,7 @@ void LLMClientInterface::handleRequestFailed(const QString &requestId, const QSt QJsonObject errorObject; errorObject["code"] = -32603; // Internal error code - errorObject["message"] = enriched; + errorObject["message"] = error; response["error"] = errorObject; emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response)); diff --git a/QuickRefactorHandler.cpp b/QuickRefactorHandler.cpp index c14a9e3..8e02e99 100644 --- a/QuickRefactorHandler.cpp +++ b/QuickRefactorHandler.cpp @@ -407,16 +407,11 @@ void QuickRefactorHandler::handleFullResponse(const QString &requestId, const QS void QuickRefactorHandler::handleRequestFailed(const QString &requestId, const QString &error) { if (requestId == m_lastRequestId) { - auto it = m_activeRequests.find(requestId); - QString enriched = (it != m_activeRequests.end() && it->provider) - ? it->provider->enrichErrorMessage(error) - : error; - m_activeRequests.remove(requestId); m_isRefactoringInProgress = false; RefactorResult result; result.success = false; - result.errorMessage = enriched; + result.errorMessage = error; result.editor = m_currentEditor; emit refactoringCompleted(result); } diff --git a/pluginllmcore/Provider.cpp b/pluginllmcore/Provider.cpp index 07458e4..a86806c 100644 --- a/pluginllmcore/Provider.cpp +++ b/pluginllmcore/Provider.cpp @@ -4,10 +4,6 @@ #include "Provider.hpp" #include -#include -#include -#include -#include #include #include @@ -50,30 +46,4 @@ void Provider::cancelRequest(const RequestID &requestId) return client()->tools(); } -QString Provider::enrichErrorMessage(const QString &error) const -{ - auto *c = client(); - - if (qobject_cast<::LLMQore::MistralClient *>(c)) - return error; - - const bool isOpenAICompatible = qobject_cast<::LLMQore::OpenAIClient *>(c) - || qobject_cast<::LLMQore::OpenAIResponsesClient *>(c) - || qobject_cast<::LLMQore::LlamaCppClient *>(c); - if (!isOpenAICompatible) - return error; - - const QString baseUrl = c->url(); - const QString path = QUrl(baseUrl).path(); - const bool hasV1Segment = path.contains("/v1/") || path.endsWith("/v1"); - if (hasV1Segment) - return error; - - return error - + tr("\n\nHint: Your base URL (%1) does not contain a '/v1' path segment. " - "Most OpenAI-compatible servers require it (e.g., %1/v1). " - "Try updating the URL in settings.") - .arg(baseUrl); -} - } // namespace QodeAssist::PluginLLMCore diff --git a/pluginllmcore/Provider.hpp b/pluginllmcore/Provider.hpp index 2095ba3..b0929ab 100644 --- a/pluginllmcore/Provider.hpp +++ b/pluginllmcore/Provider.hpp @@ -60,8 +60,6 @@ public: const QUrl &url, const QJsonObject &payload, const QString &endpoint); void cancelRequest(const RequestID &requestId); ::LLMQore::ToolsManager *toolsManager() const; - - QString enrichErrorMessage(const QString &error) const; }; } // namespace QodeAssist::PluginLLMCore diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp index fff5329..c70a3f7 100644 --- a/providers/LMStudioProvider.cpp +++ b/providers/LMStudioProvider.cpp @@ -28,7 +28,7 @@ LMStudioProvider::LMStudioProvider(QObject *parent) QString LMStudioProvider::name() const { - return "LM Studio"; + return "LM Studio (Chat Completions)"; } QString LMStudioProvider::apiKey() const diff --git a/providers/LMStudioResponsesProvider.cpp b/providers/LMStudioResponsesProvider.cpp new file mode 100644 index 0000000..16ea792 --- /dev/null +++ b/providers/LMStudioResponsesProvider.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2024-2026 Petr Mironychev +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "LMStudioResponsesProvider.hpp" + +#include + +#include "logger/Logger.hpp" +#include "settings/ChatAssistantSettings.hpp" +#include "settings/CodeCompletionSettings.hpp" +#include "settings/GeneralSettings.hpp" +#include "settings/ProviderSettings.hpp" +#include "settings/QuickRefactorSettings.hpp" +#include "tools/ToolsRegistration.hpp" + +#include +#include +#include + +namespace QodeAssist::Providers { + +LMStudioResponsesProvider::LMStudioResponsesProvider(QObject *parent) + : PluginLLMCore::Provider(parent) + , m_client(new ::LLMQore::OpenAIResponsesClient(QString(), QString(), QString(), this)) +{ + Tools::registerQodeAssistTools(m_client->tools()); +} + +QString LMStudioResponsesProvider::name() const +{ + return "LM Studio (Responses API)"; +} + +QString LMStudioResponsesProvider::apiKey() const +{ + return {}; +} + +QString LMStudioResponsesProvider::url() const +{ + return "http://localhost:1234"; +} + +void LMStudioResponsesProvider::prepareRequest( + QJsonObject &request, + PluginLLMCore::PromptTemplate *prompt, + PluginLLMCore::ContextData context, + PluginLLMCore::RequestType type, + bool isToolsEnabled, + bool isThinkingEnabled) +{ + if (!prompt->isSupportProvider(providerID())) { + LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name())); + } + + prompt->prepareRequest(request, context); + + auto applyModelParams = [&request](const auto &settings) { + request["max_output_tokens"] = settings.maxTokens(); + + if (settings.useTopP()) { + request["top_p"] = settings.topP(); + } + }; + + auto applyThinkingMode = [&request](const auto &settings) { + QString effortStr = settings.openAIResponsesReasoningEffort.stringValue().toLower(); + if (effortStr.isEmpty()) { + effortStr = "medium"; + } + + QJsonObject reasoning; + reasoning["effort"] = effortStr; + request["reasoning"] = reasoning; + request["max_output_tokens"] = settings.thinkingMaxTokens(); + request["store"] = true; + + QJsonArray include; + include.append("reasoning.encrypted_content"); + request["include"] = include; + }; + + if (type == PluginLLMCore::RequestType::CodeCompletion) { + applyModelParams(Settings::codeCompletionSettings()); + } else if (type == PluginLLMCore::RequestType::QuickRefactoring) { + const auto &qrSettings = Settings::quickRefactorSettings(); + applyModelParams(qrSettings); + + if (isThinkingEnabled) { + applyThinkingMode(qrSettings); + } + } else { + const auto &chatSettings = Settings::chatAssistantSettings(); + applyModelParams(chatSettings); + + if (isThinkingEnabled) { + applyThinkingMode(chatSettings); + } + } + + if (isToolsEnabled) { + const auto toolsDefinitions = m_client->tools()->getToolsDefinitions(); + if (!toolsDefinitions.isEmpty()) { + request["tools"] = toolsDefinitions; + LOG_MESSAGE(QString("Added %1 tools to LM Studio Responses request") + .arg(toolsDefinitions.size())); + } + } + + request["stream"] = true; +} + +QFuture> LMStudioResponsesProvider::getInstalledModels(const QString &baseUrl) +{ + QString url = baseUrl; + if (!url.endsWith(QStringLiteral("/v1"))) + url += QStringLiteral("/v1"); + m_client->setUrl(url); + m_client->setApiKey(apiKey()); + return m_client->listModels(); +} + +PluginLLMCore::RequestID LMStudioResponsesProvider::sendRequest( + const QUrl &url, const QJsonObject &payload, const QString &endpoint) +{ + const QString effectiveEndpoint + = endpoint.isEmpty() ? QStringLiteral("/v1/responses") : endpoint; + return PluginLLMCore::Provider::sendRequest(url, payload, effectiveEndpoint); +} + +PluginLLMCore::ProviderID LMStudioResponsesProvider::providerID() const +{ + return PluginLLMCore::ProviderID::OpenAIResponses; +} + +PluginLLMCore::ProviderCapabilities LMStudioResponsesProvider::capabilities() const +{ + return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking + | PluginLLMCore::ProviderCapability::Image + | PluginLLMCore::ProviderCapability::ModelListing; +} + +::LLMQore::BaseClient *LMStudioResponsesProvider::client() const +{ + return m_client; +} + +} // namespace QodeAssist::Providers diff --git a/providers/LMStudioResponsesProvider.hpp b/providers/LMStudioResponsesProvider.hpp new file mode 100644 index 0000000..e1b0f65 --- /dev/null +++ b/providers/LMStudioResponsesProvider.hpp @@ -0,0 +1,40 @@ +// Copyright (C) 2024-2026 Petr Mironychev +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace QodeAssist::Providers { + +class LMStudioResponsesProvider : public PluginLLMCore::Provider +{ + Q_OBJECT +public: + explicit LMStudioResponsesProvider(QObject *parent = nullptr); + + QString name() const override; + QString url() const override; + void prepareRequest( + QJsonObject &request, + PluginLLMCore::PromptTemplate *prompt, + PluginLLMCore::ContextData context, + PluginLLMCore::RequestType type, + bool isToolsEnabled, + bool isThinkingEnabled) override; + QFuture> getInstalledModels(const QString &url) override; + PluginLLMCore::ProviderID providerID() const override; + PluginLLMCore::ProviderCapabilities capabilities() const override; + + ::LLMQore::BaseClient *client() const override; + QString apiKey() const override; + + PluginLLMCore::RequestID sendRequest( + const QUrl &url, const QJsonObject &payload, const QString &endpoint) override; + +private: + ::LLMQore::OpenAIResponsesClient *m_client; +}; + +} // namespace QodeAssist::Providers diff --git a/providers/OllamaCompatProvider.cpp b/providers/OllamaCompatProvider.cpp new file mode 100644 index 0000000..f929d5f --- /dev/null +++ b/providers/OllamaCompatProvider.cpp @@ -0,0 +1,124 @@ +// Copyright (C) 2024-2026 Petr Mironychev +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "OllamaCompatProvider.hpp" + +#include + +#include "logger/Logger.hpp" +#include "settings/ChatAssistantSettings.hpp" +#include "settings/CodeCompletionSettings.hpp" +#include "settings/GeneralSettings.hpp" +#include "settings/ProviderSettings.hpp" +#include "settings/QuickRefactorSettings.hpp" +#include "tools/ToolsRegistration.hpp" + +#include +#include +#include + +namespace QodeAssist::Providers { + +OllamaCompatProvider::OllamaCompatProvider(QObject *parent) + : PluginLLMCore::Provider(parent) + , m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this)) +{ + Tools::registerQodeAssistTools(m_client->tools()); +} + +QString OllamaCompatProvider::name() const +{ + return "Ollama (OpenAI-compatible)"; +} + +QString OllamaCompatProvider::apiKey() const +{ + return Settings::providerSettings().ollamaBasicAuthApiKey(); +} + +QString OllamaCompatProvider::url() const +{ + return "http://localhost:11434"; +} + +void OllamaCompatProvider::prepareRequest( + QJsonObject &request, + PluginLLMCore::PromptTemplate *prompt, + PluginLLMCore::ContextData context, + PluginLLMCore::RequestType type, + bool isToolsEnabled, + bool isThinkingEnabled) +{ + if (!prompt->isSupportProvider(providerID())) { + LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name())); + } + + prompt->prepareRequest(request, context); + + auto applyModelParams = [&request](const auto &settings) { + request["max_tokens"] = settings.maxTokens(); + request["temperature"] = settings.temperature(); + + if (settings.useTopP()) + request["top_p"] = settings.topP(); + if (settings.useTopK()) + request["top_k"] = settings.topK(); + if (settings.useFrequencyPenalty()) + request["frequency_penalty"] = settings.frequencyPenalty(); + if (settings.usePresencePenalty()) + request["presence_penalty"] = settings.presencePenalty(); + }; + + if (type == PluginLLMCore::RequestType::CodeCompletion) { + applyModelParams(Settings::codeCompletionSettings()); + } else if (type == PluginLLMCore::RequestType::QuickRefactoring) { + applyModelParams(Settings::quickRefactorSettings()); + } else { + applyModelParams(Settings::chatAssistantSettings()); + } + + if (isToolsEnabled) { + auto toolsDefinitions = m_client->tools()->getToolsDefinitions(); + if (!toolsDefinitions.isEmpty()) { + request["tools"] = toolsDefinitions; + LOG_MESSAGE( + QString("Added %1 tools to OllamaCompat request").arg(toolsDefinitions.size())); + } + } +} + +QFuture> OllamaCompatProvider::getInstalledModels(const QString &baseUrl) +{ + QString url = baseUrl; + if (!url.endsWith(QStringLiteral("/v1"))) + url += QStringLiteral("/v1"); + m_client->setUrl(url); + m_client->setApiKey(apiKey()); + return m_client->listModels(); +} + +PluginLLMCore::RequestID OllamaCompatProvider::sendRequest( + const QUrl &url, const QJsonObject &payload, const QString &endpoint) +{ + const QString effectiveEndpoint + = endpoint.isEmpty() ? QStringLiteral("/v1/chat/completions") : endpoint; + return PluginLLMCore::Provider::sendRequest(url, payload, effectiveEndpoint); +} + +PluginLLMCore::ProviderID OllamaCompatProvider::providerID() const +{ + return PluginLLMCore::ProviderID::OpenAICompatible; +} + +PluginLLMCore::ProviderCapabilities OllamaCompatProvider::capabilities() const +{ + return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image + | PluginLLMCore::ProviderCapability::ModelListing; +} + +::LLMQore::BaseClient *OllamaCompatProvider::client() const +{ + return m_client; +} + +} // namespace QodeAssist::Providers diff --git a/providers/OllamaCompatProvider.hpp b/providers/OllamaCompatProvider.hpp new file mode 100644 index 0000000..b51b34b --- /dev/null +++ b/providers/OllamaCompatProvider.hpp @@ -0,0 +1,40 @@ +// Copyright (C) 2024-2026 Petr Mironychev +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace QodeAssist::Providers { + +class OllamaCompatProvider : public PluginLLMCore::Provider +{ + Q_OBJECT +public: + explicit OllamaCompatProvider(QObject *parent = nullptr); + + QString name() const override; + QString url() const override; + void prepareRequest( + QJsonObject &request, + PluginLLMCore::PromptTemplate *prompt, + PluginLLMCore::ContextData context, + PluginLLMCore::RequestType type, + bool isToolsEnabled, + bool isThinkingEnabled) override; + QFuture> getInstalledModels(const QString &url) override; + PluginLLMCore::ProviderID providerID() const override; + PluginLLMCore::ProviderCapabilities capabilities() const override; + + ::LLMQore::BaseClient *client() const override; + QString apiKey() const override; + + PluginLLMCore::RequestID sendRequest( + const QUrl &url, const QJsonObject &payload, const QString &endpoint) override; + +private: + ::LLMQore::OpenAIClient *m_client; +}; + +} // namespace QodeAssist::Providers diff --git a/providers/OllamaProvider.cpp b/providers/OllamaProvider.cpp index 576f56f..eb9c4b3 100644 --- a/providers/OllamaProvider.cpp +++ b/providers/OllamaProvider.cpp @@ -28,7 +28,7 @@ OllamaProvider::OllamaProvider(QObject *parent) QString OllamaProvider::name() const { - return "Ollama"; + return "Ollama (Native)"; } QString OllamaProvider::apiKey() const diff --git a/providers/OpenAIProvider.cpp b/providers/OpenAIProvider.cpp index cd1250a..cea5432 100644 --- a/providers/OpenAIProvider.cpp +++ b/providers/OpenAIProvider.cpp @@ -27,7 +27,7 @@ OpenAIProvider::OpenAIProvider(QObject *parent) QString OpenAIProvider::name() const { - return "OpenAI"; + return "OpenAI (Chat Completions)"; } QString OpenAIProvider::apiKey() const diff --git a/providers/OpenAIResponsesProvider.cpp b/providers/OpenAIResponsesProvider.cpp index 2bd133d..1909607 100644 --- a/providers/OpenAIResponsesProvider.cpp +++ b/providers/OpenAIResponsesProvider.cpp @@ -27,7 +27,7 @@ OpenAIResponsesProvider::OpenAIResponsesProvider(QObject *parent) QString OpenAIResponsesProvider::name() const { - return "OpenAI Responses"; + return "OpenAI (Responses API)"; } QString OpenAIResponsesProvider::apiKey() const @@ -98,24 +98,11 @@ void OpenAIResponsesProvider::prepareRequest( } if (isToolsEnabled) { - const auto toolsDefinitions - = m_client->tools()->getToolsDefinitions(); + const auto toolsDefinitions = m_client->tools()->getToolsDefinitions(); if (!toolsDefinitions.isEmpty()) { - QJsonArray responsesTools; - - for (const QJsonValue &toolValue : toolsDefinitions) { - const QJsonObject tool = toolValue.toObject(); - if (tool.contains("function")) { - const QJsonObject functionObj = tool["function"].toObject(); - QJsonObject responsesTool; - responsesTool["type"] = "function"; - responsesTool["name"] = functionObj["name"]; - responsesTool["description"] = functionObj["description"]; - responsesTool["parameters"] = functionObj["parameters"]; - responsesTools.append(responsesTool); - } - } - request["tools"] = responsesTools; + request["tools"] = toolsDefinitions; + LOG_MESSAGE(QString("Added %1 tools to OpenAI Responses request") + .arg(toolsDefinitions.size())); } } diff --git a/providers/Providers.hpp b/providers/Providers.hpp index c593730..a8832a6 100644 --- a/providers/Providers.hpp +++ b/providers/Providers.hpp @@ -8,8 +8,10 @@ #include "providers/CodestralProvider.hpp" #include "providers/GoogleAIProvider.hpp" #include "providers/LMStudioProvider.hpp" +#include "providers/LMStudioResponsesProvider.hpp" #include "providers/LlamaCppProvider.hpp" #include "providers/MistralAIProvider.hpp" +#include "providers/OllamaCompatProvider.hpp" #include "providers/OllamaProvider.hpp" #include "providers/OpenAICompatProvider.hpp" #include "providers/OpenAIProvider.hpp" @@ -22,11 +24,13 @@ inline void registerProviders() { auto &providerManager = PluginLLMCore::ProvidersManager::instance(); providerManager.registerProvider(); + providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); + providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); providerManager.registerProvider(); diff --git a/settings/CMakeLists.txt b/settings/CMakeLists.txt index b6ffdaa..292675d 100644 --- a/settings/CMakeLists.txt +++ b/settings/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(QodeAssistSettings STATIC ProjectSettings.hpp ProjectSettings.cpp ProjectSettingsPanel.hpp ProjectSettingsPanel.cpp ProviderSettings.hpp ProviderSettings.cpp + ProviderNameMigration.hpp PluginUpdater.hpp PluginUpdater.cpp UpdateDialog.hpp UpdateDialog.cpp AgentRole.hpp AgentRole.cpp diff --git a/settings/ConfigurationManager.cpp b/settings/ConfigurationManager.cpp index f5535df..82ace28 100644 --- a/settings/ConfigurationManager.cpp +++ b/settings/ConfigurationManager.cpp @@ -14,6 +14,7 @@ #include #include "Logger.hpp" +#include "ProviderNameMigration.hpp" namespace QodeAssist::Settings { @@ -101,7 +102,7 @@ QVector ConfigurationManager::getPredefinedConfigurations( AIConfiguration gpt; gpt.id = "preset_gpt"; gpt.name = "gpt-5.4"; - gpt.provider = "OpenAI Responses"; + gpt.provider = "OpenAI (Responses API)"; gpt.model = "gpt-5.4"; gpt.url = "https://api.openai.com/v1"; gpt.customEndpoint = ""; @@ -203,7 +204,7 @@ bool ConfigurationManager::loadConfigurations(ConfigurationType type) AIConfiguration config; config.id = obj["id"].toString(); config.name = obj["name"].toString(); - config.provider = obj["provider"].toString(); + config.provider = migrateProviderName(obj["provider"].toString()); config.model = obj["model"].toString(); config.templateName = obj["template"].toString(); config.url = obj["url"].toString(); diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index febdf76..10d1c86 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -27,6 +27,7 @@ #include "../Version.hpp" #include "ConfigurationManager.hpp" #include "Logger.hpp" +#include "ProviderNameMigration.hpp" #include "SettingsConstants.hpp" #include "SettingsDialog.hpp" #include "SettingsTr.hpp" @@ -95,7 +96,7 @@ GeneralSettings::GeneralSettings() qrConfigureApiKey.m_buttonText = Tr::tr("Configure API Key"); qrConfigureApiKey.m_tooltip = Tr::tr("Open Provider Settings to configure API keys"); - initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama"); + initStringAspect(ccProvider, Constants::CC_PROVIDER, TrConstants::PROVIDER, "Ollama (Native)"); ccProvider.setReadOnly(true); ccSelectProvider.m_buttonText = TrConstants::SELECT; @@ -143,7 +144,10 @@ GeneralSettings::GeneralSettings() preset1Language.addOption("python"); initStringAspect( - ccPreset1Provider, Constants::CC_PRESET1_PROVIDER, TrConstants::PROVIDER, "Ollama"); + ccPreset1Provider, + Constants::CC_PRESET1_PROVIDER, + TrConstants::PROVIDER, + "Ollama (Native)"); ccPreset1Provider.setReadOnly(true); ccPreset1SelectProvider.m_buttonText = TrConstants::SELECT; @@ -170,7 +174,7 @@ GeneralSettings::GeneralSettings() ccPreset1SelectTemplate.m_buttonText = TrConstants::SELECT; // chat assistance - initStringAspect(caProvider, Constants::CA_PROVIDER, TrConstants::PROVIDER, "Ollama"); + initStringAspect(caProvider, Constants::CA_PROVIDER, TrConstants::PROVIDER, "Ollama (Native)"); caProvider.setReadOnly(true); caSelectProvider.m_buttonText = TrConstants::SELECT; @@ -207,7 +211,7 @@ GeneralSettings::GeneralSettings() caOpenConfigFolder.m_isCompact = true; // quick refactor settings - initStringAspect(qrProvider, Constants::QR_PROVIDER, TrConstants::PROVIDER, "Ollama"); + initStringAspect(qrProvider, Constants::QR_PROVIDER, TrConstants::PROVIDER, "Ollama (Native)"); qrProvider.setReadOnly(true); qrSelectProvider.m_buttonText = TrConstants::SELECT; @@ -257,6 +261,17 @@ GeneralSettings::GeneralSettings() readSettings(); + auto migrateProviderAspect = [](Utils::StringAspect &aspect) { + const QString migrated = migrateProviderName(aspect.value()); + if (migrated != aspect.value()) + aspect.setValue(migrated); + }; + migrateProviderAspect(ccProvider); + migrateProviderAspect(ccPreset1Provider); + migrateProviderAspect(caProvider); + migrateProviderAspect(qrProvider); + writeSettings(); + Logger::instance().setLoggingEnabled(enableLogging()); setupConnections(); diff --git a/settings/ProviderNameMigration.hpp b/settings/ProviderNameMigration.hpp new file mode 100644 index 0000000..56d6eb1 --- /dev/null +++ b/settings/ProviderNameMigration.hpp @@ -0,0 +1,29 @@ +// Copyright (C) 2024-2026 Petr Mironychev +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace QodeAssist::Settings { + +// Maps legacy provider names (used before multi-API providers were introduced +// with parenthetical suffixes) to their current canonical names. Returns the +// input unchanged if it is not a known legacy name. +inline QString migrateProviderName(const QString &oldName) +{ + static const QHash renames{ + {QStringLiteral("Ollama"), QStringLiteral("Ollama (Native)")}, + {QStringLiteral("Ollama Compatible"), QStringLiteral("Ollama (OpenAI-compatible)")}, + {QStringLiteral("OpenAI"), QStringLiteral("OpenAI (Chat Completions)")}, + {QStringLiteral("OpenAI Responses"), QStringLiteral("OpenAI (Responses API)")}, + {QStringLiteral("LM Studio"), QStringLiteral("LM Studio (Chat Completions)")}, + {QStringLiteral("LM Studio Responses"), QStringLiteral("LM Studio (Responses API)")}, + }; + + const auto it = renames.constFind(oldName); + return it != renames.constEnd() ? it.value() : oldName; +} + +} // namespace QodeAssist::Settings