From e2e13f0f38b494e782710408473a132eed80785a Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:13:05 +0100 Subject: [PATCH] refactor: Improve http client (#319) --- ConfigurationManager.cpp | 30 +-- llmcore/HttpClient.cpp | 361 +++++++++++++++----------- llmcore/HttpClient.hpp | 39 +-- llmcore/Provider.hpp | 7 +- providers/ClaudeProvider.cpp | 46 ++-- providers/ClaudeProvider.hpp | 5 +- providers/GoogleAIProvider.cpp | 46 +--- providers/GoogleAIProvider.hpp | 5 +- providers/LMStudioProvider.cpp | 52 ++-- providers/LMStudioProvider.hpp | 5 +- providers/LlamaCppProvider.cpp | 19 +- providers/LlamaCppProvider.hpp | 5 +- providers/MistralAIProvider.cpp | 46 ++-- providers/MistralAIProvider.hpp | 5 +- providers/OllamaProvider.cpp | 45 ++-- providers/OllamaProvider.hpp | 5 +- providers/OpenAICompatProvider.cpp | 17 +- providers/OpenAICompatProvider.hpp | 5 +- providers/OpenAIProvider.cpp | 43 +-- providers/OpenAIProvider.hpp | 5 +- providers/OpenAIResponsesProvider.cpp | 43 +-- providers/OpenAIResponsesProvider.hpp | 5 +- test/LLMClientInterfaceTests.cpp | 5 +- 23 files changed, 394 insertions(+), 450 deletions(-) diff --git a/ConfigurationManager.cpp b/ConfigurationManager.cpp index c1eb655..ba8c98f 100644 --- a/ConfigurationManager.cpp +++ b/ConfigurationManager.cpp @@ -170,28 +170,26 @@ void ConfigurationManager::selectModel() : isQuickRefactor ? m_generalSettings.qrUrl.volatileValue() : m_generalSettings.caUrl.volatileValue(); - auto &targetSettings = isCodeCompletion ? m_generalSettings.ccModel - : isPreset1 ? m_generalSettings.ccPreset1Model - : isQuickRefactor ? m_generalSettings.qrModel - : m_generalSettings.caModel; + auto *targetSettings = &(isCodeCompletion ? m_generalSettings.ccModel + : isPreset1 ? m_generalSettings.ccPreset1Model + : isQuickRefactor ? m_generalSettings.qrModel + : m_generalSettings.caModel); if (auto provider = m_providersManager.getProviderByName(providerName)) { if (!provider->supportsModelListing()) { - m_generalSettings.showModelsNotSupportedDialog(targetSettings); + m_generalSettings.showModelsNotSupportedDialog(*targetSettings); return; } - const auto modelList = provider->getInstalledModels(providerUrl); - - if (modelList.isEmpty()) { - m_generalSettings.showModelsNotFoundDialog(targetSettings); - return; - } - - QTimer::singleShot(0, &m_generalSettings, [this, modelList, &targetSettings]() { - m_generalSettings.showSelectionDialog( - modelList, targetSettings, Tr::tr("Select LLM Model"), Tr::tr("Models:")); - }); + provider->getInstalledModels(providerUrl) + .then(this, [this, targetSettings](const QList &modelList) { + if (modelList.isEmpty()) { + m_generalSettings.showModelsNotFoundDialog(*targetSettings); + return; + } + m_generalSettings.showSelectionDialog( + modelList, *targetSettings, Tr::tr("Select LLM Model"), Tr::tr("Models:")); + }); } } diff --git a/llmcore/HttpClient.cpp b/llmcore/HttpClient.cpp index fa08cae..9fbcb45 100644 --- a/llmcore/HttpClient.cpp +++ b/llmcore/HttpClient.cpp @@ -21,7 +21,6 @@ #include #include -#include #include @@ -30,9 +29,7 @@ namespace QodeAssist::LLMCore { HttpClient::HttpClient(QObject *parent) : QObject(parent) , m_manager(new QNetworkAccessManager(this)) -{ - connect(this, &HttpClient::sendRequest, this, &HttpClient::onSendRequest); -} +{} HttpClient::~HttpClient() { @@ -44,156 +41,96 @@ HttpClient::~HttpClient() m_activeRequests.clear(); } -void HttpClient::onSendRequest(const HttpRequest &request) +QFuture HttpClient::get(const QNetworkRequest &request) { - QJsonDocument doc(request.payload); - LOG_MESSAGE(QString("HttpClient: data: %1").arg(doc.toJson(QJsonDocument::Indented))); + LOG_MESSAGE(QString("HttpClient: GET %1").arg(request.url().toString())); - QNetworkReply *reply - = m_manager->post(request.networkRequest, doc.toJson(QJsonDocument::Compact)); - addActiveRequest(reply, request.requestId); + auto promise = std::make_shared>(); + promise->start(); + + QNetworkReply *reply = m_manager->get(request); + setupNonStreamingReply(reply, promise); + + return promise->future(); +} + +QFuture HttpClient::post(const QNetworkRequest &request, const QJsonObject &payload) +{ + QJsonDocument doc(payload); + LOG_MESSAGE(QString("HttpClient: POST %1, data: %2") + .arg(request.url().toString(), doc.toJson(QJsonDocument::Indented))); + + auto promise = std::make_shared>(); + promise->start(); + + QNetworkReply *reply = m_manager->post(request, doc.toJson(QJsonDocument::Compact)); + setupNonStreamingReply(reply, promise); + + return promise->future(); +} + +QFuture HttpClient::del(const QNetworkRequest &request, + std::optional payload) +{ + auto promise = std::make_shared>(); + promise->start(); + + QNetworkReply *reply; + if (payload) { + QJsonDocument doc(*payload); + LOG_MESSAGE(QString("HttpClient: DELETE %1, data: %2") + .arg(request.url().toString(), doc.toJson(QJsonDocument::Indented))); + reply = m_manager->sendCustomRequest(request, "DELETE", doc.toJson(QJsonDocument::Compact)); + } else { + LOG_MESSAGE(QString("HttpClient: DELETE %1").arg(request.url().toString())); + reply = m_manager->deleteResource(request); + } + + setupNonStreamingReply(reply, promise); + + return promise->future(); +} + +void HttpClient::setupNonStreamingReply(QNetworkReply *reply, + std::shared_ptr> promise) +{ + connect(reply, &QNetworkReply::finished, this, [this, reply, promise]() { + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QByteArray responseBody = reply->readAll(); + QNetworkReply::NetworkError networkError = reply->error(); + QString networkErrorString = reply->errorString(); + + reply->disconnect(); + reply->deleteLater(); + + LOG_MESSAGE( + QString("HttpClient: Non-streaming request - HTTP Status: %1").arg(statusCode)); + + bool hasError = (networkError != QNetworkReply::NoError) || (statusCode >= 400); + if (hasError) { + QString errorMsg = parseErrorFromResponse(statusCode, responseBody, networkErrorString); + LOG_MESSAGE(QString("HttpClient: Non-streaming request - Error: %1").arg(errorMsg)); + promise->setException( + std::make_exception_ptr(std::runtime_error(errorMsg.toStdString()))); + } else { + promise->addResult(responseBody); + } + promise->finish(); + }); +} + +void HttpClient::postStreaming(const QString &requestId, const QNetworkRequest &request, + const QJsonObject &payload) +{ + QJsonDocument doc(payload); + LOG_MESSAGE(QString("HttpClient: POST streaming %1, data: %2") + .arg(request.url().toString(), doc.toJson(QJsonDocument::Indented))); + + QNetworkReply *reply = m_manager->post(request, doc.toJson(QJsonDocument::Compact)); + addActiveRequest(reply, requestId); connect(reply, &QNetworkReply::readyRead, this, &HttpClient::onReadyRead); - connect(reply, &QNetworkReply::finished, this, &HttpClient::onFinished); -} - -void HttpClient::onReadyRead() -{ - QNetworkReply *reply = qobject_cast(sender()); - - if (!reply || reply->isFinished()) - return; - - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode >= 400) { - return; - } - - QString requestId; - { - QMutexLocker locker(&m_mutex); - bool found = false; - for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) { - if (it.value() == reply) { - requestId = it.key(); - found = true; - break; - } - } - - if (!found) - return; - } - - if (requestId.isEmpty()) - return; - - QByteArray data = reply->readAll(); - if (!data.isEmpty()) { - emit dataReceived(requestId, data); - } -} - -void HttpClient::onFinished() -{ - QNetworkReply *reply = qobject_cast(sender()); - if (!reply) - return; - - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - QByteArray responseBody = reply->readAll(); - QNetworkReply::NetworkError networkError = reply->error(); - QString networkErrorString = reply->errorString(); - - reply->disconnect(); - - QString requestId; - bool hasError = false; - QString errorMsg; - - { - QMutexLocker locker(&m_mutex); - bool found = false; - for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) { - if (it.value() == reply) { - requestId = it.key(); - m_activeRequests.erase(it); - found = true; - break; - } - } - - if (!found) { - reply->deleteLater(); - return; - } - - hasError = (networkError != QNetworkReply::NoError) || (statusCode >= 400); - - if (hasError) { - errorMsg = parseErrorFromResponse(statusCode, responseBody, networkErrorString); - } - - LOG_MESSAGE(QString("HttpClient: Request %1 - HTTP Status: %2").arg(requestId).arg(statusCode)); - - if (!responseBody.isEmpty()) { - LOG_MESSAGE(QString("HttpClient: Request %1 - Response body (%2 bytes): %3") - .arg(requestId) - .arg(responseBody.size()) - .arg(QString::fromUtf8(responseBody))); - } - - if (hasError) { - LOG_MESSAGE(QString("HttpClient: Request %1 - Error: %2").arg(requestId, errorMsg)); - } - } - - reply->deleteLater(); - - if (!requestId.isEmpty()) { - emit requestFinished(requestId, !hasError, errorMsg); - } -} - -QString HttpClient::addActiveRequest(QNetworkReply *reply, const QString &requestId) -{ - QMutexLocker locker(&m_mutex); - m_activeRequests[requestId] = reply; - LOG_MESSAGE(QString("HttpClient: Added active request: %1").arg(requestId)); - return requestId; -} - -QString HttpClient::parseErrorFromResponse( - int statusCode, const QByteArray &responseBody, const QString &networkErrorString) -{ - QString errorMsg; - - if (!responseBody.isEmpty()) { - QJsonDocument errorDoc = QJsonDocument::fromJson(responseBody); - if (!errorDoc.isNull() && errorDoc.isObject()) { - QJsonObject errorObj = errorDoc.object(); - if (errorObj.contains("error")) { - QJsonObject error = errorObj["error"].toObject(); - QString message = error["message"].toString(); - QString type = error["type"].toString(); - QString code = error["code"].toString(); - - errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(message); - if (!type.isEmpty()) - errorMsg += QString(" (type: %1)").arg(type); - if (!code.isEmpty()) - errorMsg += QString(" (code: %1)").arg(code); - } else { - errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(QString::fromUtf8(responseBody)); - } - } else { - errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(QString::fromUtf8(responseBody)); - } - } else { - errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(networkErrorString); - } - - return errorMsg; + connect(reply, &QNetworkReply::finished, this, &HttpClient::onStreamingFinished); } void HttpClient::cancelRequest(const QString &requestId) @@ -212,4 +149,128 @@ void HttpClient::cancelRequest(const QString &requestId) } } +void HttpClient::onReadyRead() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (!reply || reply->isFinished()) + return; + + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode >= 400) + return; + + QString requestId = findRequestId(reply); + if (requestId.isEmpty()) + return; + + QByteArray data = reply->readAll(); + if (!data.isEmpty()) { + emit dataReceived(requestId, data); + } +} + +void HttpClient::onStreamingFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QByteArray responseBody = reply->readAll(); + QNetworkReply::NetworkError networkError = reply->error(); + QString networkErrorString = reply->errorString(); + + reply->disconnect(); + + QString requestId; + std::optional error; + + { + QMutexLocker locker(&m_mutex); + for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) { + if (it.value() == reply) { + requestId = it.key(); + m_activeRequests.erase(it); + break; + } + } + + if (requestId.isEmpty()) { + reply->deleteLater(); + return; + } + + bool hasError = (networkError != QNetworkReply::NoError) || (statusCode >= 400); + if (hasError) { + error = parseErrorFromResponse(statusCode, responseBody, networkErrorString); + } + + LOG_MESSAGE( + QString("HttpClient: Request %1 - HTTP Status: %2").arg(requestId).arg(statusCode)); + + if (!responseBody.isEmpty()) { + LOG_MESSAGE(QString("HttpClient: Request %1 - Response body (%2 bytes): %3") + .arg(requestId) + .arg(responseBody.size()) + .arg(QString::fromUtf8(responseBody))); + } + + if (error) { + LOG_MESSAGE(QString("HttpClient: Request %1 - Error: %2").arg(requestId, *error)); + } + } + + reply->deleteLater(); + + if (!requestId.isEmpty()) { + emit requestFinished(requestId, error); + } +} + +QString HttpClient::findRequestId(QNetworkReply *reply) +{ + QMutexLocker locker(&m_mutex); + for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) { + if (it.value() == reply) + return it.key(); + } + return {}; +} + +void HttpClient::addActiveRequest(QNetworkReply *reply, const QString &requestId) +{ + QMutexLocker locker(&m_mutex); + m_activeRequests[requestId] = reply; + LOG_MESSAGE(QString("HttpClient: Added active request: %1").arg(requestId)); +} + +QString HttpClient::parseErrorFromResponse( + int statusCode, const QByteArray &responseBody, const QString &networkErrorString) +{ + if (!responseBody.isEmpty()) { + QJsonDocument errorDoc = QJsonDocument::fromJson(responseBody); + if (!errorDoc.isNull() && errorDoc.isObject()) { + QJsonObject errorObj = errorDoc.object(); + if (errorObj.contains("error")) { + QJsonObject error = errorObj["error"].toObject(); + QString message = error["message"].toString(); + QString type = error["type"].toString(); + QString code = error["code"].toString(); + + QString errorMsg = QString("HTTP %1: %2").arg(statusCode).arg(message); + if (!type.isEmpty()) + errorMsg += QString(" (type: %1)").arg(type); + if (!code.isEmpty()) + errorMsg += QString(" (code: %1)").arg(code); + return errorMsg; + } + return QString("HTTP %1: %2") + .arg(statusCode) + .arg(QString::fromUtf8(responseBody)); + } + return QString("HTTP %1: %2").arg(statusCode).arg(QString::fromUtf8(responseBody)); + } + return QString("HTTP %1: %2").arg(statusCode).arg(networkErrorString); +} + } // namespace QodeAssist::LLMCore diff --git a/llmcore/HttpClient.hpp b/llmcore/HttpClient.hpp index 157ef36..44a18c9 100644 --- a/llmcore/HttpClient.hpp +++ b/llmcore/HttpClient.hpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2025 Petr Mironychev * * This file is part of QodeAssist. @@ -19,24 +19,19 @@ #pragma once +#include + +#include #include #include -#include #include #include #include #include -#include +#include namespace QodeAssist::LLMCore { -struct HttpRequest -{ - QNetworkRequest networkRequest; - QString requestId; - QJsonObject payload; -}; - class HttpClient : public QObject { Q_OBJECT @@ -45,21 +40,33 @@ public: HttpClient(QObject *parent = nullptr); ~HttpClient(); + // Non-streaming — return QFuture with full response + QFuture get(const QNetworkRequest &request); + QFuture post(const QNetworkRequest &request, const QJsonObject &payload); + QFuture del(const QNetworkRequest &request, + std::optional payload = std::nullopt); + + // Streaming — signal-based with requestId + void postStreaming(const QString &requestId, const QNetworkRequest &request, + const QJsonObject &payload); + void cancelRequest(const QString &requestId); signals: - void sendRequest(const QodeAssist::LLMCore::HttpRequest &request); void dataReceived(const QString &requestId, const QByteArray &data); - void requestFinished(const QString &requestId, bool success, const QString &error); + void requestFinished(const QString &requestId, std::optional error); private slots: - void onSendRequest(const QodeAssist::LLMCore::HttpRequest &request); void onReadyRead(); - void onFinished(); + void onStreamingFinished(); private: - QString addActiveRequest(QNetworkReply *reply, const QString &requestId); - QString parseErrorFromResponse(int statusCode, const QByteArray &responseBody, const QString &networkErrorString); + void setupNonStreamingReply(QNetworkReply *reply, std::shared_ptr> promise); + + QString findRequestId(QNetworkReply *reply); + void addActiveRequest(QNetworkReply *reply, const QString &requestId); + QString parseErrorFromResponse(int statusCode, const QByteArray &responseBody, + const QString &networkErrorString); QNetworkAccessManager *m_manager; QHash m_activeRequests; diff --git a/llmcore/Provider.hpp b/llmcore/Provider.hpp index 993a064..dbc09c4 100644 --- a/llmcore/Provider.hpp +++ b/llmcore/Provider.hpp @@ -19,6 +19,9 @@ #pragma once +#include + +#include #include #include #include @@ -57,7 +60,7 @@ public: bool isToolsEnabled, bool isThinkingEnabled) = 0; - virtual QList getInstalledModels(const QString &url) = 0; + virtual QFuture> getInstalledModels(const QString &url) = 0; virtual QList validateRequest(const QJsonObject &request, TemplateType type) = 0; virtual QString apiKey() const = 0; virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0; @@ -81,7 +84,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) = 0; virtual void onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) = 0; signals: diff --git a/providers/ClaudeProvider.cpp b/providers/ClaudeProvider.cpp index 85ef88c..8094deb 100644 --- a/providers/ClaudeProvider.cpp +++ b/providers/ClaudeProvider.cpp @@ -19,11 +19,9 @@ #include "ClaudeProvider.hpp" -#include #include #include #include -#include #include #include "llmcore/ValidationUtils.hpp" @@ -142,11 +140,8 @@ void ClaudeProvider::prepareRequest( } } -QList ClaudeProvider::getInstalledModels(const QString &baseUrl) +QFuture> ClaudeProvider::getInstalledModels(const QString &baseUrl) { - QList models; - QNetworkAccessManager manager; - QUrl url(baseUrl + "/v1/models"); QUrlQuery query; query.addQueryItem("limit", "1000"); @@ -160,32 +155,24 @@ QList ClaudeProvider::getInstalledModels(const QString &baseUrl) request.setRawHeader("x-api-key", apiKey().toUtf8()); } - QNetworkReply *reply = manager.get(request); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - QJsonObject jsonObject = jsonResponse.object(); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); if (jsonObject.contains("data")) { QJsonArray modelArray = jsonObject["data"].toArray(); for (const QJsonValue &value : modelArray) { QJsonObject modelObject = value.toObject(); if (modelObject.contains("id")) { - QString modelId = modelObject["id"].toString(); - models.append(modelId); + models.append(modelObject["id"].toString()); } } } - } else { - LOG_MESSAGE(QString("Error fetching Claude models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching Claude models: %1").arg(e.what())); + return QList{}; + }); } QList ClaudeProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type) @@ -240,12 +227,9 @@ void ClaudeProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE(QString("ClaudeProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool ClaudeProvider::supportsTools() const @@ -289,11 +273,11 @@ void ClaudeProvider::onDataReceived( } void ClaudeProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("ClaudeProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("ClaudeProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/ClaudeProvider.hpp b/providers/ClaudeProvider.hpp index 2796244..335f6ac 100644 --- a/providers/ClaudeProvider.hpp +++ b/providers/ClaudeProvider.hpp @@ -44,7 +44,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -65,8 +65,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/GoogleAIProvider.cpp b/providers/GoogleAIProvider.cpp index 7d8a349..599780c 100644 --- a/providers/GoogleAIProvider.cpp +++ b/providers/GoogleAIProvider.cpp @@ -19,11 +19,9 @@ #include "GoogleAIProvider.hpp" -#include #include #include #include -#include #include #include "llmcore/ValidationUtils.hpp" @@ -156,29 +154,17 @@ void GoogleAIProvider::prepareRequest( } } -QList GoogleAIProvider::getInstalledModels(const QString &url) +QFuture> GoogleAIProvider::getInstalledModels(const QString &url) { - QList models; - - QNetworkAccessManager manager; QNetworkRequest request(QString("%1/models?key=%2").arg(url, apiKey())); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QNetworkReply *reply = manager.get(request); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - QJsonObject jsonObject = jsonResponse.object(); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); if (jsonObject.contains("models")) { QJsonArray modelArray = jsonObject["models"].toArray(); - models.clear(); - for (const QJsonValue &value : modelArray) { QJsonObject modelObject = value.toObject(); if (modelObject.contains("name")) { @@ -190,12 +176,11 @@ QList GoogleAIProvider::getInstalledModels(const QString &url) } } } - } else { - LOG_MESSAGE(QString("Error fetching Google AI models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching Google AI models: %1").arg(e.what())); + return QList{}; + }); } QList GoogleAIProvider::validateRequest( @@ -254,13 +239,10 @@ void GoogleAIProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE( QString("GoogleAIProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool GoogleAIProvider::supportsTools() const @@ -327,11 +309,11 @@ void GoogleAIProvider::onDataReceived( } void GoogleAIProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("GoogleAIProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("GoogleAIProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/GoogleAIProvider.hpp b/providers/GoogleAIProvider.hpp index 3c0c080..f228c99 100644 --- a/providers/GoogleAIProvider.hpp +++ b/providers/GoogleAIProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -62,8 +62,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp index 8092829..18b0dcf 100644 --- a/providers/LMStudioProvider.cpp +++ b/providers/LMStudioProvider.cpp @@ -27,11 +27,9 @@ #include "settings/GeneralSettings.hpp" #include "settings/ProviderSettings.hpp" -#include #include #include #include -#include namespace QodeAssist::Providers { @@ -71,35 +69,24 @@ bool LMStudioProvider::supportsModelListing() const return true; } -QList LMStudioProvider::getInstalledModels(const QString &url) +QFuture> LMStudioProvider::getInstalledModels(const QString &url) { - QList models; - QNetworkAccessManager manager; QNetworkRequest request(QString("%1%2").arg(url, "/v1/models")); - QNetworkReply *reply = manager.get(request); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); + QJsonArray modelArray = jsonObject["data"].toArray(); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - QJsonObject jsonObject = jsonResponse.object(); - QJsonArray modelArray = jsonObject["data"].toArray(); - - for (const QJsonValue &value : modelArray) { - QJsonObject modelObject = value.toObject(); - QString modelId = modelObject["id"].toString(); - models.append(modelId); + for (const QJsonValue &value : modelArray) { + QJsonObject modelObject = value.toObject(); + models.append(modelObject["id"].toString()); } - } else { - LOG_MESSAGE(QString("Error fetching LMStudio models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching LMStudio models: %1").arg(e.what())); + return QList{}; + }); } QList LMStudioProvider::validateRequest( @@ -149,13 +136,10 @@ void LMStudioProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE( QString("LMStudioProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool LMStudioProvider::supportsTools() const @@ -195,11 +179,11 @@ void LMStudioProvider::onDataReceived( } void LMStudioProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("LMStudioProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("LMStudioProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/LMStudioProvider.hpp b/providers/LMStudioProvider.hpp index 0d84846..2aeb140 100644 --- a/providers/LMStudioProvider.hpp +++ b/providers/LMStudioProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -61,8 +61,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/LlamaCppProvider.cpp b/providers/LlamaCppProvider.cpp index c15c357..563076b 100644 --- a/providers/LlamaCppProvider.cpp +++ b/providers/LlamaCppProvider.cpp @@ -26,11 +26,9 @@ #include "settings/QuickRefactorSettings.hpp" #include "settings/GeneralSettings.hpp" -#include #include #include #include -#include namespace QodeAssist::Providers { @@ -121,9 +119,9 @@ void LlamaCppProvider::prepareRequest( } } -QList LlamaCppProvider::getInstalledModels(const QString &url) +QFuture> LlamaCppProvider::getInstalledModels(const QString &) { - return {}; + return QtFuture::makeReadyFuture(QList{}); } QList LlamaCppProvider::validateRequest( @@ -192,13 +190,10 @@ void LlamaCppProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE( QString("LlamaCppProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool LlamaCppProvider::supportsTools() const @@ -250,11 +245,11 @@ void LlamaCppProvider::onDataReceived( } void LlamaCppProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("LlamaCppProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("LlamaCppProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/LlamaCppProvider.hpp b/providers/LlamaCppProvider.hpp index 5635f87..b88216e 100644 --- a/providers/LlamaCppProvider.hpp +++ b/providers/LlamaCppProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -61,8 +61,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/MistralAIProvider.cpp b/providers/MistralAIProvider.cpp index 200d52a..59baef2 100644 --- a/providers/MistralAIProvider.cpp +++ b/providers/MistralAIProvider.cpp @@ -27,11 +27,9 @@ #include "settings/GeneralSettings.hpp" #include "settings/ProviderSettings.hpp" -#include #include #include #include -#include namespace QodeAssist::Providers { @@ -71,43 +69,32 @@ bool MistralAIProvider::supportsModelListing() const return true; } -QList MistralAIProvider::getInstalledModels(const QString &url) +QFuture> MistralAIProvider::getInstalledModels(const QString &url) { - QList models; - QNetworkAccessManager manager; QNetworkRequest request(QString("%1/v1/models").arg(url)); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (!apiKey().isEmpty()) { request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8()); } - QNetworkReply *reply = manager.get(request); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - QJsonObject jsonObject = jsonResponse.object(); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); if (jsonObject.contains("data") && jsonObject["object"].toString() == "list") { QJsonArray modelArray = jsonObject["data"].toArray(); for (const QJsonValue &value : modelArray) { QJsonObject modelObject = value.toObject(); if (modelObject.contains("id")) { - QString modelId = modelObject["id"].toString(); - models.append(modelId); + models.append(modelObject["id"].toString()); } } } - } else { - LOG_MESSAGE(QString("Error fetching Mistral AI models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching Mistral AI models: %1").arg(e.what())); + return QList{}; + }); } QList MistralAIProvider::validateRequest( @@ -170,13 +157,10 @@ void MistralAIProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE( QString("MistralAIProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool MistralAIProvider::supportsTools() const @@ -216,11 +200,11 @@ void MistralAIProvider::onDataReceived( } void MistralAIProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("MistralAIProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("MistralAIProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/MistralAIProvider.hpp b/providers/MistralAIProvider.hpp index 7b92b4c..6a68c03 100644 --- a/providers/MistralAIProvider.hpp +++ b/providers/MistralAIProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -61,8 +61,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/OllamaProvider.cpp b/providers/OllamaProvider.cpp index 92ac504..e29590f 100644 --- a/providers/OllamaProvider.cpp +++ b/providers/OllamaProvider.cpp @@ -22,8 +22,6 @@ #include #include #include -#include -#include #include "llmcore/ValidationUtils.hpp" #include "logger/Logger.hpp" @@ -147,35 +145,25 @@ void OllamaProvider::prepareRequest( } } -QList OllamaProvider::getInstalledModels(const QString &url) +QFuture> OllamaProvider::getInstalledModels(const QString &url) { - QList models; - QNetworkAccessManager manager; QNetworkRequest request(QString("%1%2").arg(url, "/api/tags")); prepareNetworkRequest(request); - QNetworkReply *reply = manager.get(request); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - QJsonObject jsonObject = jsonResponse.object(); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); QJsonArray modelArray = jsonObject["models"].toArray(); for (const QJsonValue &value : modelArray) { QJsonObject modelObject = value.toObject(); - QString modelName = modelObject["name"].toString(); - models.append(modelName); + models.append(modelObject["name"].toString()); } - } else { - LOG_MESSAGE(QString("Error fetching models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching models: %1").arg(e.what())); + return QList{}; + }); } QList OllamaProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type) @@ -248,12 +236,9 @@ void OllamaProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE(QString("OllamaProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool OllamaProvider::supportsTools() const @@ -312,11 +297,11 @@ void OllamaProvider::onDataReceived( } void OllamaProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("OllamaProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("OllamaProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/OllamaProvider.hpp b/providers/OllamaProvider.hpp index ef4fd2c..34d2665 100644 --- a/providers/OllamaProvider.hpp +++ b/providers/OllamaProvider.hpp @@ -44,7 +44,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -63,8 +63,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp index 6f486a1..e71dc31 100644 --- a/providers/OpenAICompatProvider.cpp +++ b/providers/OpenAICompatProvider.cpp @@ -122,9 +122,9 @@ void OpenAICompatProvider::prepareRequest( } } -QList OpenAICompatProvider::getInstalledModels(const QString &url) +QFuture> OpenAICompatProvider::getInstalledModels(const QString &) { - return QStringList(); + return QtFuture::makeReadyFuture(QList{}); } QList OpenAICompatProvider::validateRequest( @@ -178,13 +178,10 @@ void OpenAICompatProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE( QString("OpenAICompatProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool OpenAICompatProvider::supportsTools() const @@ -224,11 +221,11 @@ void OpenAICompatProvider::onDataReceived( } void OpenAICompatProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("OpenAICompatProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("OpenAICompatProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/OpenAICompatProvider.hpp b/providers/OpenAICompatProvider.hpp index c8e4769..e6b70f3 100644 --- a/providers/OpenAICompatProvider.hpp +++ b/providers/OpenAICompatProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -61,8 +61,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/OpenAIProvider.cpp b/providers/OpenAIProvider.cpp index 685ecee..aa5df7f 100644 --- a/providers/OpenAIProvider.cpp +++ b/providers/OpenAIProvider.cpp @@ -27,11 +27,9 @@ #include "settings/GeneralSettings.hpp" #include "settings/ProviderSettings.hpp" -#include #include #include #include -#include namespace QodeAssist::Providers { @@ -141,26 +139,17 @@ void OpenAIProvider::prepareRequest( } } -QList OpenAIProvider::getInstalledModels(const QString &url) +QFuture> OpenAIProvider::getInstalledModels(const QString &url) { - QList models; - QNetworkAccessManager manager; QNetworkRequest request(QString("%1/v1/models").arg(url)); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (!apiKey().isEmpty()) { request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8()); } - QNetworkReply *reply = manager.get(request); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - QJsonObject jsonObject = jsonResponse.object(); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); if (jsonObject.contains("data")) { QJsonArray modelArray = jsonObject["data"].toArray(); @@ -176,12 +165,11 @@ QList OpenAIProvider::getInstalledModels(const QString &url) } } } - } else { - LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(e.what())); + return QList{}; + }); } QList OpenAIProvider::validateRequest(const QJsonObject &request, LLMCore::TemplateType type) @@ -235,12 +223,9 @@ void OpenAIProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - LOG_MESSAGE(QString("OpenAIProvider: Sending request %1 to %2").arg(requestId, url.toString())); - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool OpenAIProvider::supportsTools() const @@ -280,11 +265,11 @@ void OpenAIProvider::onDataReceived( } void OpenAIProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("OpenAIProvider request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("OpenAIProvider request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/OpenAIProvider.hpp b/providers/OpenAIProvider.hpp index eafeae5..52535a7 100644 --- a/providers/OpenAIProvider.hpp +++ b/providers/OpenAIProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -61,8 +61,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/providers/OpenAIResponsesProvider.cpp b/providers/OpenAIResponsesProvider.cpp index 250aa01..fa7c444 100644 --- a/providers/OpenAIResponsesProvider.cpp +++ b/providers/OpenAIResponsesProvider.cpp @@ -28,11 +28,9 @@ #include "settings/ProviderSettings.hpp" #include "settings/QuickRefactorSettings.hpp" -#include #include #include #include -#include namespace QodeAssist::Providers { @@ -158,26 +156,17 @@ void OpenAIResponsesProvider::prepareRequest( request["stream"] = true; } -QList OpenAIResponsesProvider::getInstalledModels(const QString &url) +QFuture> OpenAIResponsesProvider::getInstalledModels(const QString &url) { - QList models; - QNetworkAccessManager manager; QNetworkRequest request(QString("%1/v1/models").arg(url)); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); if (!apiKey().isEmpty()) { request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8()); } - QNetworkReply *reply = manager.get(request); - QEventLoop loop; - QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); - - if (reply->error() == QNetworkReply::NoError) { - const QByteArray responseData = reply->readAll(); - const QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); - const QJsonObject jsonObject = jsonResponse.object(); + return httpClient()->get(request).then([](const QByteArray &data) { + QList models; + const QJsonObject jsonObject = QJsonDocument::fromJson(data).object(); if (jsonObject.contains("data")) { const QJsonArray modelArray = jsonObject["data"].toArray(); @@ -200,12 +189,11 @@ QList OpenAIResponsesProvider::getInstalledModels(const QString &url) } } } - } else { - LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(reply->errorString())); - } - - reply->deleteLater(); - return models; + return models; + }).onFailed([](const std::exception &e) { + LOG_MESSAGE(QString("Error fetching OpenAI models: %1").arg(e.what())); + return QList{}; + }); } QList OpenAIResponsesProvider::validateRequest( @@ -280,10 +268,7 @@ void OpenAIResponsesProvider::sendRequest( QNetworkRequest networkRequest(url); prepareNetworkRequest(networkRequest); - LLMCore::HttpRequest - request{.networkRequest = networkRequest, .requestId = requestId, .payload = payload}; - - emit httpClient()->sendRequest(request); + httpClient()->postStreaming(requestId, networkRequest, payload); } bool OpenAIResponsesProvider::supportsTools() const @@ -344,11 +329,11 @@ void OpenAIResponsesProvider::onDataReceived( } void OpenAIResponsesProvider::onRequestFinished( - const QodeAssist::LLMCore::RequestID &requestId, bool success, const QString &error) + const QodeAssist::LLMCore::RequestID &requestId, std::optional error) { - if (!success) { - LOG_MESSAGE(QString("OpenAIResponses request %1 failed: %2").arg(requestId, error)); - emit requestFailed(requestId, error); + if (error) { + LOG_MESSAGE(QString("OpenAIResponses request %1 failed: %2").arg(requestId, *error)); + emit requestFailed(requestId, *error); cleanupRequest(requestId); return; } diff --git a/providers/OpenAIResponsesProvider.hpp b/providers/OpenAIResponsesProvider.hpp index 39d1317..420985f 100644 --- a/providers/OpenAIResponsesProvider.hpp +++ b/providers/OpenAIResponsesProvider.hpp @@ -43,7 +43,7 @@ public: LLMCore::RequestType type, bool isToolsEnabled, bool isThinkingEnabled) override; - QList getInstalledModels(const QString &url) override; + QFuture> getInstalledModels(const QString &url) override; QList validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override; QString apiKey() const override; void prepareNetworkRequest(QNetworkRequest &networkRequest) const override; @@ -62,8 +62,7 @@ public slots: const QodeAssist::LLMCore::RequestID &requestId, const QByteArray &data) override; void onRequestFinished( const QodeAssist::LLMCore::RequestID &requestId, - bool success, - const QString &error) override; + std::optional error) override; private slots: void onToolExecutionComplete( diff --git a/test/LLMClientInterfaceTests.cpp b/test/LLMClientInterfaceTests.cpp index bdbd7ee..1f93507 100644 --- a/test/LLMClientInterfaceTests.cpp +++ b/test/LLMClientInterfaceTests.cpp @@ -80,7 +80,10 @@ public: return true; } - QList getInstalledModels(const QString &url) override { return {}; } + QFuture> getInstalledModels(const QString &) override + { + return QtFuture::makeReadyFuture(QList{}); + } QStringList validateRequest( const QJsonObject &request, LLMCore::TemplateType templateType) override