From 851e681cf50558c1225fcd296269a6ebd2926a98 Mon Sep 17 00:00:00 2001
From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com>
Date: Sat, 30 Aug 2025 18:34:10 +0200
Subject: [PATCH] feat: Add new http client
---
llmcore/CMakeLists.txt | 1 +
llmcore/HttpClient.cpp | 150 +++++++++++++++++++++++++++++++++++++++++
llmcore/HttpClient.hpp | 69 +++++++++++++++++++
3 files changed, 220 insertions(+)
create mode 100644 llmcore/HttpClient.cpp
create mode 100644 llmcore/HttpClient.hpp
diff --git a/llmcore/CMakeLists.txt b/llmcore/CMakeLists.txt
index 2aaf91a..151f9ac 100644
--- a/llmcore/CMakeLists.txt
+++ b/llmcore/CMakeLists.txt
@@ -16,6 +16,7 @@ add_library(LLMCore STATIC
OpenAIMessage.hpp OpenAIMessage.cpp
ValidationUtils.hpp ValidationUtils.cpp
ProviderID.hpp
+ HttpClient.hpp HttpClient.cpp
)
target_link_libraries(LLMCore
diff --git a/llmcore/HttpClient.cpp b/llmcore/HttpClient.cpp
new file mode 100644
index 0000000..1e41044
--- /dev/null
+++ b/llmcore/HttpClient.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "HttpClient.hpp"
+
+#include
+#include
+#include
+
+#include
+
+namespace QodeAssist::LLMCore {
+
+HttpClient::HttpClient(QObject *parent)
+ : QObject(parent)
+ , m_manager(new QNetworkAccessManager(this))
+{
+ connect(this, &HttpClient::sendRequest, this, &HttpClient::onSendRequest);
+}
+
+HttpClient::~HttpClient()
+{
+ QMutexLocker locker(&m_mutex);
+ for (auto *reply : std::as_const(m_activeRequests)) {
+ reply->abort();
+ reply->deleteLater();
+ }
+ m_activeRequests.clear();
+}
+
+void HttpClient::onSendRequest(const HttpRequest &request)
+{
+ QNetworkRequest networkRequest(request.url);
+ networkRequest.setTransferTimeout(300000);
+ networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ networkRequest.setRawHeader("Accept", "text/event-stream");
+ networkRequest.setRawHeader("Cache-Control", "no-cache");
+ networkRequest.setRawHeader("Connection", "keep-alive");
+
+ if (request.headers.has_value()) {
+ for (const auto &[headername, value] : request.headers->asKeyValueRange()) {
+ networkRequest.setRawHeader(headername.toUtf8(), value.toUtf8());
+ }
+ }
+
+ QJsonDocument doc(request.payload);
+ LOG_MESSAGE(QString("HttpClient: Sending POST to %1").arg(request.url.toString()));
+ LOG_MESSAGE(QString("HttpClient: data: %1").arg(doc.toJson(QJsonDocument::Indented)));
+
+ QNetworkReply *reply = m_manager->post(networkRequest, doc.toJson(QJsonDocument::Compact));
+ addActiveRequest(reply, request.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)
+ return;
+
+ QString requestId;
+ {
+ QMutexLocker locker(&m_mutex);
+ for (auto it = m_activeRequests.begin(); it != m_activeRequests.end(); ++it) {
+ if (it.value() == reply) {
+ requestId = it.key();
+ break;
+ }
+ }
+ }
+
+ 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;
+
+ QString requestId;
+ bool hasError = false;
+ QString errorMsg;
+ {
+ 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 (reply->error() != QNetworkReply::NoError) {
+ hasError = true;
+ errorMsg = reply->errorString();
+ }
+ }
+
+ 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;
+}
+
+void HttpClient::cancelRequest(const QString &requestId)
+{
+ QMutexLocker locker(&m_mutex);
+ if (auto it = m_activeRequests.find(requestId); it != m_activeRequests.end()) {
+ it.value()->abort();
+ it.value()->deleteLater();
+ m_activeRequests.erase(it);
+ LOG_MESSAGE(QString("HttpClient: Cancelled request: %1").arg(requestId));
+ }
+}
+
+} // namespace QodeAssist::LLMCore
diff --git a/llmcore/HttpClient.hpp b/llmcore/HttpClient.hpp
new file mode 100644
index 0000000..18ac023
--- /dev/null
+++ b/llmcore/HttpClient.hpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace QodeAssist::LLMCore {
+
+struct HttpRequest
+{
+ QUrl url;
+ QString requestId;
+ QJsonObject payload;
+ std::optional> headers;
+};
+
+class HttpClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ HttpClient(QObject *parent = nullptr);
+ ~HttpClient();
+
+ 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);
+
+private slots:
+ void onSendRequest(const QodeAssist::LLMCore::HttpRequest &request);
+ void onReadyRead();
+ void onFinished();
+
+private:
+ QString addActiveRequest(QNetworkReply *reply, const QString &requestId);
+
+ QNetworkAccessManager *m_manager;
+ QHash m_activeRequests;
+ mutable QMutex m_mutex;
+};
+
+} // namespace QodeAssist::LLMCore