feat: LM Studio response API and Ollama OpenAI API

This commit is contained in:
Petr Mironychev
2026-04-23 03:26:52 +02:00
parent 7a551ed384
commit 25c4d5f185
19 changed files with 423 additions and 72 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -4,10 +4,6 @@
#include "Provider.hpp"
#include <LLMQore/BaseClient.hpp>
#include <LLMQore/LlamaCppClient.hpp>
#include <LLMQore/MistralClient.hpp>
#include <LLMQore/OpenAIClient.hpp>
#include <LLMQore/OpenAIResponsesClient.hpp>
#include <LLMQore/ToolsManager.hpp>
#include <QJsonDocument>
@@ -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

View File

@@ -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

View File

@@ -28,7 +28,7 @@ LMStudioProvider::LMStudioProvider(QObject *parent)
QString LMStudioProvider::name() const
{
return "LM Studio";
return "LM Studio (Chat Completions)";
}
QString LMStudioProvider::apiKey() const

View File

@@ -0,0 +1,148 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#include "LMStudioResponsesProvider.hpp"
#include <LLMQore/ToolsManager.hpp>
#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 <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
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<QList<QString>> 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

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <LLMQore/OpenAIResponsesClient.hpp>
#include <pluginllmcore/Provider.hpp>
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<QList<QString>> 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

View File

@@ -0,0 +1,124 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#include "OllamaCompatProvider.hpp"
#include <LLMQore/ToolsManager.hpp>
#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 <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
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<QList<QString>> 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

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <LLMQore/OpenAIClient.hpp>
#include <pluginllmcore/Provider.hpp>
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<QList<QString>> 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

View File

@@ -28,7 +28,7 @@ OllamaProvider::OllamaProvider(QObject *parent)
QString OllamaProvider::name() const
{
return "Ollama";
return "Ollama (Native)";
}
QString OllamaProvider::apiKey() const

View File

@@ -27,7 +27,7 @@ OpenAIProvider::OpenAIProvider(QObject *parent)
QString OpenAIProvider::name() const
{
return "OpenAI";
return "OpenAI (Chat Completions)";
}
QString OpenAIProvider::apiKey() const

View File

@@ -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()));
}
}

View File

@@ -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<OllamaProvider>();
providerManager.registerProvider<OllamaCompatProvider>();
providerManager.registerProvider<ClaudeProvider>();
providerManager.registerProvider<OpenAIProvider>();
providerManager.registerProvider<OpenAIResponsesProvider>();
providerManager.registerProvider<OpenAICompatProvider>();
providerManager.registerProvider<LMStudioProvider>();
providerManager.registerProvider<LMStudioResponsesProvider>();
providerManager.registerProvider<OpenRouterProvider>();
providerManager.registerProvider<MistralAIProvider>();
providerManager.registerProvider<GoogleAIProvider>();

View File

@@ -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

View File

@@ -14,6 +14,7 @@
#include <coreplugin/icore.h>
#include "Logger.hpp"
#include "ProviderNameMigration.hpp"
namespace QodeAssist::Settings {
@@ -101,7 +102,7 @@ QVector<AIConfiguration> 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();

View File

@@ -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();

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QHash>
#include <QString>
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<QString, QString> 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