Update LLMQore to v0.0.4 (#339)

This commit is contained in:
Petr Mironychev
2026-04-19 11:58:54 +02:00
committed by GitHub
parent 6c05f0d594
commit ede2c01eb7
91 changed files with 381 additions and 2225 deletions

2
.gitignore vendored
View File

@@ -78,3 +78,5 @@ CMakeLists.txt.user*
/.cursor
/.vscode
.qtc_clangd/compile_commands.json
CLAUDE.md
/.claude

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "sources/external/llmcore"]
path = sources/external/llmcore
url = https://github.com/Palm1r/llmcore.git
[submodule "sources/external/llmqore"]
path = sources/external/llmqore
url = https://github.com/Palm1r/llmqore.git

View File

@@ -34,7 +34,7 @@ add_definitions(
-DQODEASSIST_QT_CREATOR_VERSION_PATCH=${QODEASSIST_QT_CREATOR_VERSION_PATCH}
)
add_subdirectory(sources/external/llmcore)
add_subdirectory(sources/external/llmqore)
add_subdirectory(pluginllmcore)
add_subdirectory(settings)
add_subdirectory(logger)
@@ -62,7 +62,7 @@ add_qtc_plugin(QodeAssist
QtCreator::ExtensionSystem
QtCreator::Utils
QtCreator::CPlusPlus
LLMCore
LLMQore
PluginLLMCore
QodeAssistChatViewplugin
SOURCES

View File

@@ -85,7 +85,7 @@ target_link_libraries(QodeAssistChatView
Context
QodeAssistUIControlsplugin
QodeAssistLogger
LLMCore
LLMQore
)
target_include_directories(QodeAssistChatView

View File

@@ -19,7 +19,7 @@
#include "ChatCompressor.hpp"
#include <LLMCore/BaseClient.hpp>
#include <LLMQore/BaseClient.hpp>
#include "ChatModel.hpp"
#include "GeneralSettings.hpp"
#include "PromptTemplateManager.hpp"
@@ -83,23 +83,16 @@ void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *ch
connectProviderSignals();
QUrl requestUrl;
QJsonObject payload;
if (m_provider->providerID() == PluginLLMCore::ProviderID::GoogleAI) {
requestUrl = QUrl(QString("%1/models/%2:streamGenerateContent?alt=sse")
.arg(Settings::generalSettings().caUrl(),
Settings::generalSettings().caModel()));
} else {
requestUrl = QUrl(QString("%1%2").arg(Settings::generalSettings().caUrl(),
m_provider->chatEndpoint()));
payload["model"] = Settings::generalSettings().caModel();
payload["stream"] = true;
}
QJsonObject payload{
{"model", Settings::generalSettings().caModel()}, {"stream", true}};
buildRequestPayload(payload, promptTemplate);
m_currentRequestId = m_provider->sendRequest(requestUrl, payload);
const QString customEndpoint = Settings::generalSettings().caCustomEndpoint();
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
: promptTemplate->endpoint();
m_currentRequestId = m_provider->sendRequest(
QUrl(Settings::generalSettings().caUrl()), payload, endpoint);
LOG_MESSAGE(QString("Starting compression request: %1").arg(m_currentRequestId));
}
@@ -271,21 +264,21 @@ void ChatCompressor::connectProviderSignals()
m_connections.append(connect(
c,
&::LLMCore::BaseClient::chunkReceived,
&::LLMQore::BaseClient::chunkReceived,
this,
&ChatCompressor::onPartialResponseReceived,
Qt::UniqueConnection));
m_connections.append(connect(
c,
&::LLMCore::BaseClient::requestCompleted,
&::LLMQore::BaseClient::requestCompleted,
this,
&ChatCompressor::onFullResponseReceived,
Qt::UniqueConnection));
m_connections.append(connect(
c,
&::LLMCore::BaseClient::requestFailed,
&::LLMQore::BaseClient::requestFailed,
this,
&ChatCompressor::onRequestFailed,
Qt::UniqueConnection));

View File

@@ -1400,8 +1400,6 @@ void ChatRootView::applyConfiguration(const QString &configName)
settings.caModel.setValue(config.model);
settings.caTemplate.setValue(config.templateName);
settings.caUrl.setValue(config.url);
settings.caEndpointMode.setValue(
settings.caEndpointMode.indexForDisplay(config.endpointMode));
settings.caCustomEndpoint.setValue(config.customEndpoint);
settings.writeSettings();

View File

@@ -19,7 +19,7 @@
#include "ClientInterface.hpp"
#include <LLMCore/BaseClient.hpp>
#include <LLMQore/BaseClient.hpp>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/target.h>
@@ -42,7 +42,7 @@
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "tools/TodoTool.hpp"
@@ -51,7 +51,6 @@
#include "GeneralSettings.hpp"
#include "Logger.hpp"
#include "ProvidersManager.hpp"
#include "RequestConfig.hpp"
#include "ToolsSettings.hpp"
#include <RulesLoader.hpp>
#include <context/ChangesManager.h>
@@ -248,26 +247,11 @@ void ClientInterface::sendMessage(
context.history = messages;
PluginLLMCore::LLMConfig config;
config.requestType = PluginLLMCore::RequestType::Chat;
config.provider = provider;
config.promptTemplate = promptTemplate;
if (provider->providerID() == PluginLLMCore::ProviderID::GoogleAI) {
QString stream = QString{"streamGenerateContent?alt=sse"};
config.url = QUrl(QString("%1/models/%2:%3")
.arg(
Settings::generalSettings().caUrl(),
Settings::generalSettings().caModel(),
stream));
} else {
config.url
= QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
config.providerRequest
= {{"model", Settings::generalSettings().caModel()}, {"stream", true}};
}
QJsonObject payload{
{"model", Settings::generalSettings().caModel()}, {"stream", true}};
config.provider->prepareRequest(
config.providerRequest,
provider->prepareRequest(
payload,
promptTemplate,
context,
PluginLLMCore::RequestType::Chat,
@@ -276,42 +260,46 @@ void ClientInterface::sendMessage(
connect(
provider->client(),
&::LLMCore::BaseClient::chunkReceived,
&::LLMQore::BaseClient::chunkReceived,
this,
&ClientInterface::handlePartialResponse,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::requestCompleted,
&::LLMQore::BaseClient::requestCompleted,
this,
&ClientInterface::handleFullResponse,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::requestFailed,
&::LLMQore::BaseClient::requestFailed,
this,
&ClientInterface::handleRequestFailed,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::toolStarted,
&::LLMQore::BaseClient::toolStarted,
this,
&ClientInterface::handleToolExecutionStarted,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::toolResultReady,
&::LLMQore::BaseClient::toolResultReady,
this,
&ClientInterface::handleToolExecutionCompleted,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::thinkingBlockReceived,
&::LLMQore::BaseClient::thinkingBlockReceived,
this,
&ClientInterface::handleThinkingBlockReceived,
Qt::UniqueConnection);
auto requestId = provider->sendRequest(config.url, config.providerRequest);
const QString customEndpoint = Settings::generalSettings().caCustomEndpoint();
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
: promptTemplate->endpoint();
auto requestId
= provider->sendRequest(QUrl(Settings::generalSettings().caUrl()), payload, endpoint);
QJsonObject request{{"id", requestId}};
m_activeRequests[requestId] = {request, provider};
@@ -480,8 +468,10 @@ void ClientInterface::handleRequestFailed(const QString &requestId, const QStrin
if (it == m_activeRequests.end())
return;
LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, error));
emit errorOccurred(error);
QString enriched = it->provider ? it->provider->enrichErrorMessage(error) : error;
LOG_MESSAGE(QString("Chat request %1 failed: %2").arg(requestId, enriched));
emit errorOccurred(enriched);
m_activeRequests.erase(it);
m_accumulatedResponses.remove(requestId);

View File

@@ -19,7 +19,7 @@
#include "LLMClientInterface.hpp"
#include <LLMCore/BaseClient.hpp>
#include <LLMQore/BaseClient.hpp>
#include <QJsonDocument>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@@ -30,7 +30,6 @@
#include "logger/Logger.hpp"
#include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include <pluginllmcore/RequestConfig.hpp>
#include <pluginllmcore/RulesLoader.hpp>
namespace QodeAssist {
@@ -86,17 +85,19 @@ void LLMClientInterface::handleRequestFailed(const QString &requestId, const QSt
if (it == m_activeRequests.end())
return;
LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, error));
// Send LSP error response to client
const RequestContext &ctx = it.value();
QString enriched = ctx.provider ? ctx.provider->enrichErrorMessage(error) : error;
LOG_MESSAGE(QString("Request %1 failed: %2").arg(requestId, enriched));
// Send LSP error response to client
QJsonObject response;
response["jsonrpc"] = "2.0";
response[LanguageServerProtocol::idKey] = ctx.originalRequest["id"];
QJsonObject errorObject;
errorObject["code"] = -32603; // Internal error code
errorObject["message"] = error;
errorObject["message"] = enriched;
response["error"] = errorObject;
emit messageReceived(LanguageServerProtocol::JsonRpcMessage(response));
@@ -269,25 +270,11 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
return;
}
// TODO refactor to dynamic presets system
PluginLLMCore::LLMConfig config;
config.requestType = PluginLLMCore::RequestType::CodeCompletion;
config.provider = provider;
config.promptTemplate = promptTemplate;
// TODO refactor networking
if (provider->providerID() == PluginLLMCore::ProviderID::GoogleAI) {
QString stream = QString{"streamGenerateContent?alt=sse"};
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
} else {
config.url = QUrl(
QString("%1%2").arg(url, endpoint(provider, promptTemplate->type(), isPreset1Active)));
config.providerRequest = {{"model", modelName}, {"stream", true}};
}
config.multiLineCompletion = m_completeSettings.multiLineCompletion();
QJsonObject payload{{"model", modelName}, {"stream", true}};
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
const auto stopWords = QJsonArray::fromStringList(promptTemplate->stopWords());
if (!stopWords.isEmpty())
config.providerRequest["stop"] = stopWords;
payload["stop"] = stopWords;
QString systemPrompt;
if (m_completeSettings.useSystemPrompt())
@@ -341,8 +328,8 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
updatedContext.history = messages;
}
config.provider->prepareRequest(
config.providerRequest,
provider->prepareRequest(
payload,
promptTemplate,
updatedContext,
PluginLLMCore::RequestType::CodeCompletion,
@@ -351,18 +338,19 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
connect(
provider->client(),
&::LLMCore::BaseClient::requestCompleted,
&::LLMQore::BaseClient::requestCompleted,
this,
&LLMClientInterface::handleFullResponse,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::requestFailed,
&::LLMQore::BaseClient::requestFailed,
this,
&LLMClientInterface::handleRequestFailed,
Qt::UniqueConnection);
auto requestId = provider->sendRequest(config.url, config.providerRequest);
auto requestId
= provider->sendRequest(QUrl(url), payload, resolveEndpoint(promptTemplate, isPreset1Active));
m_activeRequests[requestId] = {request, provider};
m_performanceLogger.startTimeMeasurement(requestId);
}
@@ -381,24 +369,12 @@ PluginLLMCore::ContextData LLMClientInterface::prepareContext(
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
}
QString LLMClientInterface::endpoint(
PluginLLMCore::Provider *provider, PluginLLMCore::TemplateType type, bool isLanguageSpecify)
QString LLMClientInterface::resolveEndpoint(
PluginLLMCore::PromptTemplate *promptTemplate, bool isLanguageSpecify) const
{
QString endpoint;
auto endpointMode = isLanguageSpecify ? m_generalSettings.ccPreset1EndpointMode.stringValue()
: m_generalSettings.ccEndpointMode.stringValue();
if (endpointMode == "Auto") {
endpoint = type == PluginLLMCore::TemplateType::FIM ? provider->completionEndpoint()
: provider->chatEndpoint();
} else if (endpointMode == "Custom") {
endpoint = isLanguageSpecify ? m_generalSettings.ccPreset1CustomEndpoint()
: m_generalSettings.ccCustomEndpoint();
} else if (endpointMode == "FIM") {
endpoint = provider->completionEndpoint();
} else if (endpointMode == "Chat") {
endpoint = provider->chatEndpoint();
}
return endpoint;
const QString custom = isLanguageSpecify ? m_generalSettings.ccPreset1CustomEndpoint()
: m_generalSettings.ccCustomEndpoint();
return !custom.isEmpty() ? custom : promptTemplate->endpoint();
}
Context::ContextManager *LLMClientInterface::contextManager() const

View File

@@ -87,7 +87,9 @@ private:
PluginLLMCore::ContextData prepareContext(
const QJsonObject &request, const Context::DocumentInfo &documentInfo);
QString endpoint(PluginLLMCore::Provider *provider, PluginLLMCore::TemplateType type, bool isLanguageSpecify);
QString resolveEndpoint(
PluginLLMCore::PromptTemplate *promptTemplate, bool isLanguageSpecify) const;
const Settings::CodeCompletionSettings &m_completeSettings;
const Settings::GeneralSettings &m_generalSettings;

View File

@@ -19,7 +19,7 @@
#include "QuickRefactorHandler.hpp"
#include <LLMCore/BaseClient.hpp>
#include <LLMQore/BaseClient.hpp>
#include <QJsonArray>
#include <QJsonDocument>
#include <QUuid>
@@ -30,7 +30,6 @@
#include <context/Utils.hpp>
#include <pluginllmcore/PromptTemplateManager.hpp>
#include <pluginllmcore/ProvidersManager.hpp>
#include <pluginllmcore/RequestConfig.hpp>
#include <pluginllmcore/RulesLoader.hpp>
#include <logger/Logger.hpp>
#include <settings/ChatAssistantSettings.hpp>
@@ -141,32 +140,15 @@ void QuickRefactorHandler::prepareAndSendRequest(
return;
}
PluginLLMCore::LLMConfig config;
config.requestType = PluginLLMCore::RequestType::QuickRefactoring;
config.provider = provider;
config.promptTemplate = promptTemplate;
config.url = QString("%1%2").arg(settings.qrUrl(), provider->chatEndpoint());
if (provider->providerID() == PluginLLMCore::ProviderID::GoogleAI) {
QString stream = QString{"streamGenerateContent?alt=sse"};
config.url = QUrl(QString("%1/models/%2:%3")
.arg(
Settings::generalSettings().qrUrl(),
Settings::generalSettings().qrModel(),
stream));
} else {
config.url
= QString("%1%2").arg(Settings::generalSettings().qrUrl(), provider->chatEndpoint());
config.providerRequest
= {{"model", Settings::generalSettings().qrModel()}, {"stream", true}};
}
QJsonObject payload{
{"model", Settings::generalSettings().qrModel()}, {"stream", true}};
PluginLLMCore::ContextData context = prepareContext(editor, range, instructions);
bool enableTools = Settings::quickRefactorSettings().useTools();
bool enableThinking = Settings::quickRefactorSettings().useThinking();
provider->prepareRequest(
config.providerRequest,
payload,
promptTemplate,
context,
PluginLLMCore::RequestType::QuickRefactoring,
@@ -177,19 +159,23 @@ void QuickRefactorHandler::prepareAndSendRequest(
connect(
provider->client(),
&::LLMCore::BaseClient::requestCompleted,
&::LLMQore::BaseClient::requestCompleted,
this,
&QuickRefactorHandler::handleFullResponse,
Qt::UniqueConnection);
connect(
provider->client(),
&::LLMCore::BaseClient::requestFailed,
&::LLMQore::BaseClient::requestFailed,
this,
&QuickRefactorHandler::handleRequestFailed,
Qt::UniqueConnection);
auto requestId = provider->sendRequest(config.url, config.providerRequest);
const QString customEndpoint = Settings::generalSettings().qrCustomEndpoint();
const QString endpoint = !customEndpoint.isEmpty() ? customEndpoint
: promptTemplate->endpoint();
auto requestId
= provider->sendRequest(QUrl(Settings::generalSettings().qrUrl()), payload, endpoint);
m_lastRequestId = requestId;
QJsonObject request{{"id", requestId}};
@@ -437,11 +423,16 @@ 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 = error;
result.errorMessage = enriched;
result.editor = m_currentEditor;
emit refactoringCompleted(result);
}

View File

@@ -1,73 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "BaseTool.hpp"
namespace QodeAssist::PluginLLMCore {
BaseTool::BaseTool(QObject *parent)
: QObject(parent)
{}
QJsonObject BaseTool::customizeForOpenAI(const QJsonObject &baseDefinition) const
{
QJsonObject function;
function["name"] = name();
function["description"] = description();
function["parameters"] = baseDefinition;
QJsonObject tool;
tool["type"] = "function";
tool["function"] = function;
return tool;
}
QJsonObject BaseTool::customizeForClaude(const QJsonObject &baseDefinition) const
{
QJsonObject tool;
tool["name"] = name();
tool["description"] = description();
tool["input_schema"] = baseDefinition;
return tool;
}
QJsonObject BaseTool::customizeForOllama(const QJsonObject &baseDefinition) const
{
return customizeForOpenAI(baseDefinition);
}
QJsonObject BaseTool::customizeForGoogle(const QJsonObject &baseDefinition) const
{
QJsonObject functionDeclaration;
functionDeclaration["name"] = name();
functionDeclaration["description"] = description();
functionDeclaration["parameters"] = baseDefinition;
QJsonArray functionDeclarations;
functionDeclarations.append(functionDeclaration);
QJsonObject tool;
tool["function_declarations"] = functionDeclarations;
return tool;
}
} // namespace QodeAssist::PluginLLMCore

View File

@@ -1,70 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFuture>
#include <QJsonArray>
#include <QJsonObject>
#include <QObject>
#include <QString>
namespace QodeAssist::PluginLLMCore {
enum class ToolSchemaFormat { OpenAI, Claude, Ollama, Google };
enum ToolPermission {
None = 0,
FileSystemRead = 1 << 0,
FileSystemWrite = 1 << 1,
NetworkAccess = 1 << 2
};
Q_DECLARE_FLAGS(ToolPermissions, ToolPermission)
Q_DECLARE_OPERATORS_FOR_FLAGS(ToolPermissions)
enum class RunToolsFilter {
ALL, // Run all tools (no filtering)
OnlyRead, // Run only read tools (FileSystemRead + None)
OnlyWrite, // Run only write tools (FileSystemWrite)
OnlyNetworking // Run only network tools (NetworkAccess)
};
class BaseTool : public QObject
{
Q_OBJECT
public:
explicit BaseTool(QObject *parent = nullptr);
~BaseTool() override = default;
virtual QString name() const = 0;
virtual QString stringName() const = 0;
virtual QString description() const = 0;
virtual QJsonObject getDefinition(ToolSchemaFormat format) const = 0;
virtual ToolPermissions requiredPermissions() const = 0;
virtual QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) = 0;
protected:
virtual QJsonObject customizeForOpenAI(const QJsonObject &baseDefinition) const;
virtual QJsonObject customizeForClaude(const QJsonObject &baseDefinition) const;
virtual QJsonObject customizeForOllama(const QJsonObject &baseDefinition) const;
virtual QJsonObject customizeForGoogle(const QJsonObject &baseDefinition) const;
};
} // namespace QodeAssist::PluginLLMCore

View File

@@ -9,12 +9,10 @@ add_library(PluginLLMCore STATIC
PromptProviderFim.hpp
PromptTemplate.hpp
PromptTemplateManager.hpp PromptTemplateManager.cpp
RequestConfig.hpp
ProviderID.hpp
HttpClient.hpp HttpClient.cpp
DataBuffers.hpp
SSEBuffer.hpp SSEBuffer.cpp
BaseTool.hpp BaseTool.cpp
ContentBlocks.hpp
RulesLoader.hpp RulesLoader.cpp
ResponseCleaner.hpp
@@ -27,7 +25,7 @@ target_link_libraries(PluginLLMCore
QtCreator::Core
QtCreator::Utils
QtCreator::ExtensionSystem
LLMCore
LLMQore
PRIVATE
QodeAssistLogger
)

View File

@@ -1,51 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QHash>
#include <QJsonArray>
#include <QJsonObject>
#include <QString>
#include "BaseTool.hpp"
namespace QodeAssist::PluginLLMCore {
class IToolsManager
{
public:
virtual ~IToolsManager() = default;
virtual void executeToolCall(
const QString &requestId,
const QString &toolId,
const QString &toolName,
const QJsonObject &input) = 0;
virtual QJsonArray getToolsDefinitions(
ToolSchemaFormat format,
RunToolsFilter filter = RunToolsFilter::ALL) const = 0;
virtual void cleanupRequest(const QString &requestId) = 0;
virtual void setCurrentSessionId(const QString &sessionId) = 0;
virtual void clearTodoSession(const QString &sessionId) = 0;
};
} // namespace QodeAssist::LLMCore

View File

@@ -40,5 +40,13 @@ public:
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
virtual QString description() const = 0;
virtual bool isSupportProvider(ProviderID id) const = 0;
// Endpoint path this template expects to be sent to. Empty string
// (default) means "let the provider's client use its standard chat
// path" (/chat/completions, /api/chat, /v1/messages, ...). Templates
// producing non-chat payload shapes (e.g. {prompt, suffix} for
// Mistral FIM, {input_prefix, input_suffix} for llama.cpp infill)
// must override this to the path their payload is valid for.
virtual QString endpoint() const { return {}; }
};
} // namespace QodeAssist::PluginLLMCore

View File

@@ -19,8 +19,14 @@
#include "Provider.hpp"
#include <LLMCore/BaseClient.hpp>
#include <LLMCore/ToolsManager.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>
#include <Logger.hpp>
@@ -30,19 +36,21 @@ Provider::Provider(QObject *parent)
: QObject(parent)
{}
RequestID Provider::sendRequest(const QUrl &url, const QJsonObject &payload)
RequestID Provider::sendRequest(
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
{
auto *c = client();
QUrl baseUrl(url);
baseUrl.setPath("");
c->setUrl(baseUrl.toString());
c->setUrl(url.toString());
c->setApiKey(apiKey());
auto requestId = c->sendMessage(payload);
auto requestId = c->sendMessage(payload, endpoint);
LOG_MESSAGE(
QString("%1: Sending request %2 to %3").arg(name(), requestId, url.toString()));
QString("%1: Sending request %2 to %3%4").arg(name(), requestId, url.toString(), endpoint));
LOG_MESSAGE(
QString("%1: Payload:\n%2")
.arg(name(), QString::fromUtf8(QJsonDocument(payload).toJson(QJsonDocument::Indented))));
return requestId;
}
@@ -53,9 +61,35 @@ void Provider::cancelRequest(const RequestID &requestId)
client()->cancelRequest(requestId);
}
::LLMCore::ToolsManager *Provider::toolsManager() const
::LLMQore::ToolsManager *Provider::toolsManager() const
{
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

@@ -26,11 +26,10 @@
#include <utils/environment.h>
#include "ContextData.hpp"
#include "IToolsManager.hpp"
#include "PromptTemplate.hpp"
#include "RequestType.hpp"
namespace LLMCore {
namespace LLMQore {
class BaseClient;
class ToolsManager;
}
@@ -58,8 +57,6 @@ public:
virtual QString name() const = 0;
virtual QString url() const = 0;
virtual QString completionEndpoint() const = 0;
virtual QString chatEndpoint() const = 0;
virtual void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -72,12 +69,15 @@ public:
virtual ProviderID providerID() const = 0;
virtual ProviderCapabilities capabilities() const { return {}; }
virtual ::LLMCore::BaseClient *client() const = 0;
virtual ::LLMQore::BaseClient *client() const = 0;
virtual QString apiKey() const = 0;
RequestID sendRequest(const QUrl &url, const QJsonObject &payload);
virtual RequestID sendRequest(
const QUrl &url, const QJsonObject &payload, const QString &endpoint);
void cancelRequest(const RequestID &requestId);
::LLMCore::ToolsManager *toolsManager() const;
::LLMQore::ToolsManager *toolsManager() const;
QString enrichErrorMessage(const QString &error) const;
};
} // namespace QodeAssist::PluginLLMCore

View File

@@ -1,40 +0,0 @@
/*
* Copyright (C) 2024-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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "PromptTemplate.hpp"
#include "Provider.hpp"
#include "RequestType.hpp"
#include <QJsonObject>
#include <QUrl>
namespace QodeAssist::PluginLLMCore {
struct LLMConfig
{
QUrl url;
Provider *provider;
PromptTemplate *promptTemplate;
QJsonObject providerRequest;
RequestType requestType;
bool multiLineCompletion;
};
} // namespace QodeAssist::PluginLLMCore

View File

@@ -1,30 +0,0 @@
/*
* Copyright (C) 2024-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 <https://www.gnu.org/licenses/>.
*/
#include "RequestHandlerBase.hpp"
namespace QodeAssist::LLMCore {
RequestHandlerBase::RequestHandlerBase(QObject *parent)
: QObject(parent)
{}
RequestHandlerBase::~RequestHandlerBase() = default;
} // namespace QodeAssist::LLMCore

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2024-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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "RequestConfig.hpp"
#include <QJsonObject>
#include <QObject>
namespace QodeAssist::LLMCore {
class RequestHandlerBase : public QObject
{
Q_OBJECT
public:
explicit RequestHandlerBase(QObject *parent = nullptr);
~RequestHandlerBase() override;
virtual void sendLLMRequest(const LLMConfig &config, const QJsonObject &request) = 0;
virtual bool cancelRequest(const QString &id) = 0;
signals:
void completionReceived(const QString &completion, const QJsonObject &request, bool isComplete);
void requestFinished(const QString &requestId, bool success, const QString &errorString);
void requestCancelled(const QString &id);
};
} // namespace QodeAssist::LLMCore

View File

@@ -23,7 +23,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
@@ -37,7 +37,7 @@ namespace QodeAssist::Providers {
ClaudeProvider::ClaudeProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::ClaudeClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::ClaudeClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -57,16 +57,6 @@ QString ClaudeProvider::url() const
return "https://api.anthropic.com";
}
QString ClaudeProvider::completionEndpoint() const
{
return "/v1/messages";
}
QString ClaudeProvider::chatEndpoint() const
{
return "/v1/messages";
}
void ClaudeProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -151,7 +141,7 @@ PluginLLMCore::ProviderCapabilities ClaudeProvider::capabilities() const
| PluginLLMCore::ProviderCapability::ModelListing;
}
::LLMCore::BaseClient *ClaudeProvider::client() const
::LLMQore::BaseClient *ClaudeProvider::client() const
{
return m_client;
}

View File

@@ -21,7 +21,7 @@
#include <pluginllmcore/Provider.hpp>
#include <LLMCore/ClaudeClient.hpp>
#include <LLMQore/ClaudeClient.hpp>
namespace QodeAssist::Providers {
@@ -33,8 +33,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -46,11 +44,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::ClaudeClient *m_client;
::LLMQore::ClaudeClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -19,7 +19,7 @@
#include "GoogleAIProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include <QJsonArray>
#include "tools/ToolsRegistration.hpp"
@@ -38,7 +38,7 @@ namespace QodeAssist::Providers {
GoogleAIProvider::GoogleAIProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::GoogleAIClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::GoogleAIClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -58,16 +58,6 @@ QString GoogleAIProvider::url() const
return "https://generativelanguage.googleapis.com/v1beta";
}
QString GoogleAIProvider::completionEndpoint() const
{
return {};
}
QString GoogleAIProvider::chatEndpoint() const
{
return {};
}
void GoogleAIProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -165,7 +155,25 @@ PluginLLMCore::ProviderCapabilities GoogleAIProvider::capabilities() const
| PluginLLMCore::ProviderCapability::ModelListing;
}
::LLMCore::BaseClient *GoogleAIProvider::client() const
PluginLLMCore::RequestID GoogleAIProvider::sendRequest(
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
{
// Gemini takes the model from the URL path and streaming from the
// action suffix (:streamGenerateContent vs :generateContent), and
// rejects unknown top-level body fields. The shared call-site seeds
// payload with {model, stream}; consume them here into client state
// before they hit the wire.
QJsonObject cleaned = payload;
if (cleaned.contains("model")) {
m_client->setModel(cleaned["model"].toString());
cleaned.remove("model");
}
cleaned.remove("stream");
return PluginLLMCore::Provider::sendRequest(url, cleaned, endpoint);
}
::LLMQore::BaseClient *GoogleAIProvider::client() const
{
return m_client;
}

View File

@@ -21,7 +21,7 @@
#include <pluginllmcore/Provider.hpp>
#include <LLMCore/GoogleAIClient.hpp>
#include <LLMQore/GoogleAIClient.hpp>
namespace QodeAssist::Providers {
@@ -33,8 +33,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -46,11 +44,14 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
PluginLLMCore::RequestID sendRequest(
const QUrl &url, const QJsonObject &payload, const QString &endpoint) override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::GoogleAIClient *m_client;
::LLMQore::GoogleAIClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -19,7 +19,7 @@
#include "LMStudioProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "tools/ToolsRegistration.hpp"
#include "logger/Logger.hpp"
@@ -37,7 +37,7 @@ namespace QodeAssist::Providers {
LMStudioProvider::LMStudioProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::OpenAIClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -54,17 +54,7 @@ QString LMStudioProvider::apiKey() const
QString LMStudioProvider::url() const
{
return "http://localhost:1234";
}
QString LMStudioProvider::completionEndpoint() const
{
return "/v1/completions";
}
QString LMStudioProvider::chatEndpoint() const
{
return "/v1/chat/completions";
return "http://localhost:1234/v1";
}
QFuture<QList<QString>> LMStudioProvider::getInstalledModels(const QString &url)
@@ -130,7 +120,7 @@ void LMStudioProvider::prepareRequest(
}
}
::LLMCore::BaseClient *LMStudioProvider::client() const
::LLMQore::BaseClient *LMStudioProvider::client() const
{
return m_client;
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/OpenAIClient.hpp>
#include <LLMQore/OpenAIClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
@@ -32,8 +32,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -45,11 +43,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::OpenAIClient *m_client;
::LLMQore::OpenAIClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -19,7 +19,7 @@
#include "LlamaCppProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
#include "settings/CodeCompletionSettings.hpp"
@@ -35,7 +35,7 @@ namespace QodeAssist::Providers {
LlamaCppProvider::LlamaCppProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::LlamaCppClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::LlamaCppClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -55,16 +55,6 @@ QString LlamaCppProvider::url() const
return "http://localhost:8080";
}
QString LlamaCppProvider::completionEndpoint() const
{
return "/infill";
}
QString LlamaCppProvider::chatEndpoint() const
{
return "/v1/chat/completions";
}
void LlamaCppProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -125,7 +115,7 @@ PluginLLMCore::ProviderCapabilities LlamaCppProvider::capabilities() const
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image;
}
::LLMCore::BaseClient *LlamaCppProvider::client() const
::LLMQore::BaseClient *LlamaCppProvider::client() const
{
return m_client;
}

View File

@@ -21,7 +21,7 @@
#include <pluginllmcore/Provider.hpp>
#include <LLMCore/LlamaCppClient.hpp>
#include <LLMQore/LlamaCppClient.hpp>
namespace QodeAssist::Providers {
@@ -33,8 +33,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -46,11 +44,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::LlamaCppClient *m_client;
::LLMQore::LlamaCppClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -19,7 +19,7 @@
#include "MistralAIProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
#include "settings/CodeCompletionSettings.hpp"
@@ -36,7 +36,7 @@ namespace QodeAssist::Providers {
MistralAIProvider::MistralAIProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::OpenAIClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::MistralClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -56,16 +56,6 @@ QString MistralAIProvider::url() const
return "https://api.mistral.ai";
}
QString MistralAIProvider::completionEndpoint() const
{
return "/v1/fim/completions";
}
QString MistralAIProvider::chatEndpoint() const
{
return "/v1/chat/completions";
}
QFuture<QList<QString>> MistralAIProvider::getInstalledModels(const QString &url)
{
m_client->setUrl(url);
@@ -129,7 +119,7 @@ void MistralAIProvider::prepareRequest(
}
}
::LLMCore::BaseClient *MistralAIProvider::client() const
::LLMQore::BaseClient *MistralAIProvider::client() const
{
return m_client;
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/OpenAIClient.hpp>
#include <LLMQore/MistralClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
@@ -32,8 +32,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -45,11 +43,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::OpenAIClient *m_client;
::LLMQore::MistralClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -19,7 +19,7 @@
#include "OllamaProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include <QJsonArray>
#include <QJsonDocument>
@@ -37,7 +37,7 @@ namespace QodeAssist::Providers {
OllamaProvider::OllamaProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::OllamaClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::OllamaClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -57,16 +57,6 @@ QString OllamaProvider::url() const
return "http://localhost:11434";
}
QString OllamaProvider::completionEndpoint() const
{
return "/api/generate";
}
QString OllamaProvider::chatEndpoint() const
{
return "/api/chat";
}
void OllamaProvider::prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -156,7 +146,7 @@ PluginLLMCore::ProviderCapabilities OllamaProvider::capabilities() const
| PluginLLMCore::ProviderCapability::ModelListing;
}
::LLMCore::BaseClient *OllamaProvider::client() const
::LLMQore::BaseClient *OllamaProvider::client() const
{
return m_client;
}

View File

@@ -21,7 +21,7 @@
#include <pluginllmcore/Provider.hpp>
#include <LLMCore/OllamaClient.hpp>
#include <LLMQore/OllamaClient.hpp>
namespace QodeAssist::Providers {
@@ -33,8 +33,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -46,11 +44,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::OllamaClient *m_client;
::LLMQore::OllamaClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -18,7 +18,7 @@
*/
#include "OpenAICompatProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "tools/ToolsRegistration.hpp"
#include "logger/Logger.hpp"
@@ -36,7 +36,7 @@ namespace QodeAssist::Providers {
OpenAICompatProvider::OpenAICompatProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::OpenAIClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -53,17 +53,7 @@ QString OpenAICompatProvider::apiKey() const
QString OpenAICompatProvider::url() const
{
return "http://localhost:1234";
}
QString OpenAICompatProvider::completionEndpoint() const
{
return "/v1/chat/completions";
}
QString OpenAICompatProvider::chatEndpoint() const
{
return "/v1/chat/completions";
return "http://localhost:1234/v1";
}
void OpenAICompatProvider::prepareRequest(
@@ -127,7 +117,7 @@ PluginLLMCore::ProviderCapabilities OpenAICompatProvider::capabilities() const
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image;
}
::LLMCore::BaseClient *OpenAICompatProvider::client() const
::LLMQore::BaseClient *OpenAICompatProvider::client() const
{
return m_client;
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/OpenAIClient.hpp>
#include <LLMQore/OpenAIClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
@@ -32,8 +32,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -45,11 +43,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::OpenAIClient *m_client;
::LLMQore::OpenAIClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -19,7 +19,7 @@
#include "OpenAIProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "tools/ToolsRegistration.hpp"
#include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp"
@@ -36,7 +36,7 @@ namespace QodeAssist::Providers {
OpenAIProvider::OpenAIProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::OpenAIClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -53,17 +53,7 @@ QString OpenAIProvider::apiKey() const
QString OpenAIProvider::url() const
{
return "https://api.openai.com";
}
QString OpenAIProvider::completionEndpoint() const
{
return "/v1/chat/completions";
}
QString OpenAIProvider::chatEndpoint() const
{
return "/v1/chat/completions";
return "https://api.openai.com/v1";
}
void OpenAIProvider::prepareRequest(
@@ -158,7 +148,7 @@ PluginLLMCore::ProviderCapabilities OpenAIProvider::capabilities() const
| PluginLLMCore::ProviderCapability::ModelListing;
}
::LLMCore::BaseClient *OpenAIProvider::client() const
::LLMQore::BaseClient *OpenAIProvider::client() const
{
return m_client;
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/OpenAIClient.hpp>
#include <LLMQore/OpenAIClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
@@ -32,8 +32,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -45,11 +43,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::OpenAIClient *m_client;
::LLMQore::OpenAIClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -18,7 +18,7 @@
*/
#include "OpenAIResponsesProvider.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "tools/ToolsRegistration.hpp"
#include "logger/Logger.hpp"
@@ -36,7 +36,7 @@ namespace QodeAssist::Providers {
OpenAIResponsesProvider::OpenAIResponsesProvider(QObject *parent)
: PluginLLMCore::Provider(parent)
, m_client(new ::LLMCore::OpenAIResponsesClient(QString(), QString(), QString(), this))
, m_client(new ::LLMQore::OpenAIResponsesClient(QString(), QString(), QString(), this))
{
Tools::registerQodeAssistTools(m_client->tools());
}
@@ -53,17 +53,7 @@ QString OpenAIResponsesProvider::apiKey() const
QString OpenAIResponsesProvider::url() const
{
return "https://api.openai.com";
}
QString OpenAIResponsesProvider::completionEndpoint() const
{
return "/v1/responses";
}
QString OpenAIResponsesProvider::chatEndpoint() const
{
return "/v1/responses";
return "https://api.openai.com/v1";
}
void OpenAIResponsesProvider::prepareRequest(
@@ -179,7 +169,7 @@ PluginLLMCore::ProviderCapabilities OpenAIResponsesProvider::capabilities() cons
| PluginLLMCore::ProviderCapability::ModelListing;
}
::LLMCore::BaseClient *OpenAIResponsesProvider::client() const
::LLMQore::BaseClient *OpenAIResponsesProvider::client() const
{
return m_client;
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/OpenAIResponsesClient.hpp>
#include <LLMQore/OpenAIResponsesClient.hpp>
#include <pluginllmcore/Provider.hpp>
namespace QodeAssist::Providers {
@@ -32,8 +32,6 @@ public:
QString name() const override;
QString url() const override;
QString completionEndpoint() const override;
QString chatEndpoint() const override;
void prepareRequest(
QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt,
@@ -45,11 +43,11 @@ public:
PluginLLMCore::ProviderID providerID() const override;
PluginLLMCore::ProviderCapabilities capabilities() const override;
::LLMCore::BaseClient *client() const override;
::LLMQore::BaseClient *client() const override;
QString apiKey() const override;
private:
::LLMCore::OpenAIResponsesClient *m_client;
::LLMQore::OpenAIResponsesClient *m_client;
};
} // namespace QodeAssist::Providers

View File

@@ -1,6 +1,5 @@
add_library(QodeAssistSettings STATIC
GeneralSettings.hpp GeneralSettings.cpp
CustomPromptSettings.hpp CustomPromptSettings.cpp
ConfigurationManager.hpp ConfigurationManager.cpp
SettingsUtils.hpp
SettingsConstants.hpp

View File

@@ -54,7 +54,6 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
claudeOpus.provider = "Claude";
claudeOpus.model = "claude-opus-4-6";
claudeOpus.url = "https://api.anthropic.com";
claudeOpus.endpointMode = "Auto";
claudeOpus.customEndpoint = "";
claudeOpus.templateName = "Claude";
claudeOpus.type = type;
@@ -66,7 +65,6 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
claudeSonnet.provider = "Claude";
claudeSonnet.model = "claude-sonnet-4-6";
claudeSonnet.url = "https://api.anthropic.com";
claudeSonnet.endpointMode = "Auto";
claudeSonnet.customEndpoint = "";
claudeSonnet.templateName = "Claude";
claudeSonnet.type = type;
@@ -78,7 +76,6 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
claudeHaiku.provider = "Claude";
claudeHaiku.model = "claude-haiku-4-5-20251001";
claudeHaiku.url = "https://api.anthropic.com";
claudeHaiku.endpointMode = "Auto";
claudeHaiku.customEndpoint = "";
claudeHaiku.templateName = "Claude";
claudeHaiku.type = type;
@@ -90,7 +87,6 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
codestral.provider = "Codestral";
codestral.model = "codestral-latest";
codestral.url = "https://codestral.mistral.ai";
codestral.endpointMode = "Auto";
codestral.customEndpoint = "";
codestral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
codestral.type = type;
@@ -100,9 +96,8 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
mistral.id = "preset_mistral";
mistral.name = "Mistral";
mistral.provider = "Mistral AI";
mistral.model = type == ConfigurationType::CodeCompletion ? "mistral-medium-latest" : "mistral-large-latest";
mistral.model = type == ConfigurationType::CodeCompletion ? "codestral-latest" : "mistral-large-latest";
mistral.url = "https://api.mistral.ai";
mistral.endpointMode = "Auto";
mistral.customEndpoint = "";
mistral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
mistral.type = type;
@@ -114,7 +109,6 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
geminiFlash.provider = "Google AI";
geminiFlash.model = "gemini-2.5-flash";
geminiFlash.url = "https://generativelanguage.googleapis.com/v1beta";
geminiFlash.endpointMode = "Auto";
geminiFlash.customEndpoint = "";
geminiFlash.templateName = "Google AI";
geminiFlash.type = type;
@@ -125,8 +119,7 @@ QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
gpt.name = "gpt-5.4";
gpt.provider = "OpenAI Responses";
gpt.model = "gpt-5.4";
gpt.url = "https://api.openai.com";
gpt.endpointMode = "Auto";
gpt.url = "https://api.openai.com/v1";
gpt.customEndpoint = "";
gpt.templateName = "OpenAI Responses";
gpt.type = type;
@@ -230,10 +223,10 @@ bool ConfigurationManager::loadConfigurations(ConfigurationType type)
config.model = obj["model"].toString();
config.templateName = obj["template"].toString();
config.url = obj["url"].toString();
config.endpointMode = obj["endpointMode"].toString();
config.customEndpoint = obj["customEndpoint"].toString();
config.type = type;
config.formatVersion = obj.value("formatVersion").toInt(1);
config.isPredefined = false;
if (config.id.isEmpty() || config.name.isEmpty()) {
@@ -263,7 +256,6 @@ bool ConfigurationManager::saveConfiguration(const AIConfiguration &config)
obj["model"] = config.model;
obj["template"] = config.templateName;
obj["url"] = config.url;
obj["endpointMode"] = config.endpointMode;
obj["customEndpoint"] = config.customEndpoint;
QString sanitizedName = config.name;

View File

@@ -37,7 +37,7 @@ struct AIConfiguration
QString model;
QString templateName;
QString url;
QString endpointMode;
// Empty = use the template's endpoint; non-empty = override path.
QString customEndpoint;
ConfigurationType type;
int formatVersion = CONFIGURATION_FORMAT_VERSION;

View File

@@ -1,200 +0,0 @@
/*
* Copyright (C) 2024-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 <https://www.gnu.org/licenses/>.
*/
#include "CustomPromptSettings.hpp"
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/layoutbuilder.h>
#include <QFileDialog>
#include <QJsonParseError>
#include <QMessageBox>
#include "SettingsConstants.hpp"
#include "SettingsTr.hpp"
#include "SettingsUtils.hpp"
namespace QodeAssist::Settings {
CustomPromptSettings &customPromptSettings()
{
static CustomPromptSettings settings;
return settings;
}
CustomPromptSettings::CustomPromptSettings()
{
setAutoApply(false);
setDisplayName(Tr::tr("Custom Prompt"));
customJsonLabel.setLabelText("Custom JSON Template:");
customJsonLabel.setDisplayStyle(Utils::StringAspect::LabelDisplay);
customJsonLegend.setLabelText(Tr::tr(R"(Prompt components:
- model is set on General Page
- {{QODE_INSTRUCTIONS}}: Placeholder for specific instructions or context.
- {{QODE_PREFIX}}: Will be replaced with the actual code before the cursor.
- {{QODE_SUFFIX}}: Will be replaced with the actual code after the cursor.
)"));
customJsonTemplate.setSettingsKey(Constants::CUSTOM_JSON_TEMPLATE);
customJsonTemplate.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
customJsonTemplate.setDefaultValue(R"({
"prompt": "{{QODE_INSTRUCTIONS}}<fim_prefix>{{QODE_PREFIX}}<fim_suffix>{{QODE_SUFFIX}}<fim_middle>",
"options": {
"temperature": 0.7,
"top_p": 0.95,
"top_k": 40,
"num_predict": 100,
"stop": [
"<|endoftext|>",
"<file_sep>",
"<fim_prefix>",
"<fim_suffix>",
"<fim_middle>"
],
"frequency_penalty": 0,
"presence_penalty": 0
},
"stream": true
})");
saveCustomTemplateButton.m_buttonText = (Tr::tr("Save Custom Template to JSON"));
loadCustomTemplateButton.m_buttonText = (Tr::tr("Load Custom Template from JSON"));
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
readSettings();
setupConnection();
setLayouter([this]() {
using namespace Layouting;
return Column{Group{
title(Tr::tr("Custom prompt for FIM model")),
Column{
Row{customJsonLabel, Stretch{1}, resetToDefaults},
Row{customJsonTemplate,
Column{
saveCustomTemplateButton,
loadCustomTemplateButton,
customJsonLegend,
Stretch{1}}}}}};
});
}
void CustomPromptSettings::setupConnection()
{
connect(
&resetToDefaults,
&ButtonAspect::clicked,
this,
&CustomPromptSettings::resetSettingsToDefaults);
connect(
&saveCustomTemplateButton,
&ButtonAspect::clicked,
this,
&CustomPromptSettings::saveCustomTemplate);
connect(
&loadCustomTemplateButton,
&ButtonAspect::clicked,
this,
&CustomPromptSettings::loadCustomTemplate);
}
void CustomPromptSettings::resetSettingsToDefaults()
{
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
Core::ICore::dialogParent(),
Tr::tr("Reset Settings"),
Tr::tr("Are you sure you want to reset all settings to default values?"),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
resetAspect(customJsonTemplate);
}
}
void CustomPromptSettings::saveCustomTemplate()
{
QString fileName = QFileDialog::getSaveFileName(
nullptr, Tr::tr("Save JSON Template"), QString(), Tr::tr("JSON Files (*.json)"));
if (fileName.isEmpty())
return;
QFile file(fileName);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << customJsonTemplate.value();
file.close();
QMessageBox::information(
nullptr,
Tr::tr("Save Successful"),
Tr::tr("JSON template has been saved successfully."));
} else {
QMessageBox::critical(nullptr, Tr::tr("Save Failed"), Tr::tr("Failed to save JSON template."));
}
}
void CustomPromptSettings::loadCustomTemplate()
{
QString fileName = QFileDialog::getOpenFileName(
nullptr, Tr::tr("Load JSON Template"), QString(), Tr::tr("JSON Files (*.json)"));
if (fileName.isEmpty())
return;
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
QString jsonContent = in.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContent.toUtf8(), &parseError);
if (parseError.error == QJsonParseError::NoError) {
customJsonTemplate.setVolatileValue(jsonContent);
QMessageBox::information(
nullptr,
Tr::tr("Load Successful"),
Tr::tr("JSON template has been loaded successfully."));
} else {
QMessageBox::critical(
nullptr, Tr::tr("Invalid JSON"), Tr::tr("The selected file contains invalid JSON."));
}
} else {
QMessageBox::critical(nullptr, Tr::tr("Load Failed"), Tr::tr("Failed to load JSON template."));
}
}
class CustomPromptSettingsPage : public Core::IOptionsPage
{
public:
CustomPromptSettingsPage()
{
setId(Constants::QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID);
setDisplayName(Tr::tr("Custom Prompt"));
setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY);
setSettingsProvider([] { return &customPromptSettings(); });
}
};
const CustomPromptSettingsPage customPromptSettingsPage;
} // namespace QodeAssist::Settings

View File

@@ -1,49 +0,0 @@
/*
* Copyright (C) 2024-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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <utils/aspects.h>
#include "ButtonAspect.hpp"
namespace QodeAssist::Settings {
class CustomPromptSettings : public Utils::AspectContainer
{
public:
CustomPromptSettings();
Utils::StringAspect customJsonLabel{this};
Utils::StringAspect customJsonTemplate{this};
Utils::StringAspect customJsonLegend{this};
ButtonAspect saveCustomTemplateButton{this};
ButtonAspect loadCustomTemplateButton{this};
ButtonAspect resetToDefaults{this};
private:
void setupConnection();
void resetSettingsToDefaults();
void saveCustomTemplate();
void loadCustomTemplate();
};
CustomPromptSettings &customPromptSettings();
} // namespace QodeAssist::Settings

View File

@@ -124,18 +124,10 @@ GeneralSettings::GeneralSettings()
ccSelectTemplate.m_buttonText = TrConstants::SELECT;
initStringAspect(ccUrl, Constants::CC_URL, TrConstants::URL, "http://localhost:11434");
ccUrl.setHistoryCompleter(Constants::CC_CUSTOM_ENDPOINT_HISTORY);
ccUrl.setHistoryCompleter(Constants::CC_URL_HISTORY);
ccSetUrl.m_buttonText = TrConstants::SELECT;
ccEndpointMode.setSettingsKey(Constants::CC_ENDPOINT_MODE);
ccEndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
ccEndpointMode.addOption("Auto");
ccEndpointMode.addOption("Custom");
ccEndpointMode.addOption("FIM");
ccEndpointMode.addOption("Chat");
ccEndpointMode.setDefaultValue("Auto");
initStringAspect(ccCustomEndpoint, Constants::CC_CUSTOM_ENDPOINT, TrConstants::ENDPOINT_MODE, "");
initStringAspect(ccCustomEndpoint, Constants::CC_CUSTOM_ENDPOINT, TrConstants::CUSTOM_ENDPOINT, "");
ccCustomEndpoint.setHistoryCompleter(Constants::CC_CUSTOM_ENDPOINT_HISTORY);
ccStatus.setDisplayStyle(Utils::StringAspect::LabelDisplay);
@@ -176,18 +168,10 @@ GeneralSettings::GeneralSettings()
ccPreset1Url.setHistoryCompleter(Constants::CC_PRESET1_URL_HISTORY);
ccPreset1SetUrl.m_buttonText = TrConstants::SELECT;
ccPreset1EndpointMode.setSettingsKey(Constants::CC_PRESET1_ENDPOINT_MODE);
ccPreset1EndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
ccPreset1EndpointMode.addOption("Auto");
ccPreset1EndpointMode.addOption("Custom");
ccPreset1EndpointMode.addOption("FIM");
ccPreset1EndpointMode.addOption("Chat");
ccPreset1EndpointMode.setDefaultValue("Auto");
initStringAspect(
ccPreset1CustomEndpoint,
Constants::CC_PRESET1_CUSTOM_ENDPOINT,
TrConstants::ENDPOINT_MODE,
TrConstants::CUSTOM_ENDPOINT,
"");
ccPreset1CustomEndpoint.setHistoryCompleter(Constants::CC_PRESET1_CUSTOM_ENDPOINT_HISTORY);
@@ -219,15 +203,7 @@ GeneralSettings::GeneralSettings()
caUrl.setHistoryCompleter(Constants::CA_URL_HISTORY);
caSetUrl.m_buttonText = TrConstants::SELECT;
caEndpointMode.setSettingsKey(Constants::CA_ENDPOINT_MODE);
caEndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
caEndpointMode.addOption("Auto");
caEndpointMode.addOption("Custom");
caEndpointMode.addOption("FIM");
caEndpointMode.addOption("Chat");
caEndpointMode.setDefaultValue("Auto");
initStringAspect(caCustomEndpoint, Constants::CA_CUSTOM_ENDPOINT, TrConstants::ENDPOINT_MODE, "");
initStringAspect(caCustomEndpoint, Constants::CA_CUSTOM_ENDPOINT, TrConstants::CUSTOM_ENDPOINT, "");
caCustomEndpoint.setHistoryCompleter(Constants::CA_CUSTOM_ENDPOINT_HISTORY);
caStatus.setDisplayStyle(Utils::StringAspect::LabelDisplay);
@@ -264,15 +240,7 @@ GeneralSettings::GeneralSettings()
qrUrl.setHistoryCompleter(Constants::QR_URL_HISTORY);
qrSetUrl.m_buttonText = TrConstants::SELECT;
qrEndpointMode.setSettingsKey(Constants::QR_ENDPOINT_MODE);
qrEndpointMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
qrEndpointMode.addOption("Auto");
qrEndpointMode.addOption("Custom");
qrEndpointMode.addOption("FIM");
qrEndpointMode.addOption("Chat");
qrEndpointMode.setDefaultValue("Auto");
initStringAspect(qrCustomEndpoint, Constants::QR_CUSTOM_ENDPOINT, TrConstants::ENDPOINT_MODE, "");
initStringAspect(qrCustomEndpoint, Constants::QR_CUSTOM_ENDPOINT, TrConstants::CUSTOM_ENDPOINT, "");
qrCustomEndpoint.setHistoryCompleter(Constants::QR_CUSTOM_ENDPOINT_HISTORY);
qrStatus.setDisplayStyle(Utils::StringAspect::LabelDisplay);
@@ -310,10 +278,6 @@ GeneralSettings::GeneralSettings()
setupConnections();
updatePreset1Visiblity(specifyPreset1.value());
ccCustomEndpoint.setEnabled(ccEndpointMode.stringValue() == "Custom");
ccPreset1CustomEndpoint.setEnabled(ccPreset1EndpointMode.stringValue() == "Custom");
caCustomEndpoint.setEnabled(caEndpointMode.stringValue() == "Custom");
qrCustomEndpoint.setEnabled(qrEndpointMode.stringValue() == "Custom");
setLayouter([this]() {
using namespace Layouting;
@@ -321,28 +285,28 @@ GeneralSettings::GeneralSettings()
auto ccGrid = Grid{};
ccGrid.addRow({ccProvider, ccSelectProvider});
ccGrid.addRow({ccUrl, ccSetUrl});
ccGrid.addRow({ccCustomEndpoint, ccEndpointMode});
ccGrid.addRow({ccCustomEndpoint});
ccGrid.addRow({ccModel, ccSelectModel});
ccGrid.addRow({ccTemplate, ccSelectTemplate, ccShowTemplateInfo});
auto ccPreset1Grid = Grid{};
ccPreset1Grid.addRow({ccPreset1Provider, ccPreset1SelectProvider});
ccPreset1Grid.addRow({ccPreset1Url, ccPreset1SetUrl});
ccPreset1Grid.addRow({ccPreset1CustomEndpoint, ccPreset1EndpointMode});
ccPreset1Grid.addRow({ccPreset1CustomEndpoint});
ccPreset1Grid.addRow({ccPreset1Model, ccPreset1SelectModel});
ccPreset1Grid.addRow({ccPreset1Template, ccPreset1SelectTemplate});
auto caGrid = Grid{};
caGrid.addRow({caProvider, caSelectProvider});
caGrid.addRow({caUrl, caSetUrl});
caGrid.addRow({caCustomEndpoint, caEndpointMode});
caGrid.addRow({caCustomEndpoint});
caGrid.addRow({caModel, caSelectModel});
caGrid.addRow({caTemplate, caSelectTemplate, caShowTemplateInfo});
auto qrGrid = Grid{};
qrGrid.addRow({qrProvider, qrSelectProvider});
qrGrid.addRow({qrUrl, qrSetUrl});
qrGrid.addRow({qrCustomEndpoint, qrEndpointMode});
qrGrid.addRow({qrCustomEndpoint});
qrGrid.addRow({qrModel, qrSelectModel});
qrGrid.addRow({qrTemplate, qrSelectTemplate, qrShowTemplateInfo});
@@ -589,7 +553,6 @@ void GeneralSettings::updatePreset1Visiblity(bool state)
ccPreset1SelectModel.updateVisibility(specifyPreset1.volatileValue());
ccPreset1Template.setVisible(specifyPreset1.volatileValue());
ccPreset1SelectTemplate.updateVisibility(specifyPreset1.volatileValue());
ccPreset1EndpointMode.setVisible(specifyPreset1.volatileValue());
ccPreset1CustomEndpoint.setVisible(specifyPreset1.volatileValue());
}
@@ -633,24 +596,6 @@ void GeneralSettings::setupConnections()
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
updatePreset1Visiblity(specifyPreset1.volatileValue());
});
connect(&ccEndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
ccCustomEndpoint.setEnabled(
ccEndpointMode.volatileValue() == ccEndpointMode.indexForDisplay("Custom"));
});
connect(&ccPreset1EndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
ccPreset1CustomEndpoint.setEnabled(
ccPreset1EndpointMode.volatileValue()
== ccPreset1EndpointMode.indexForDisplay("Custom"));
});
connect(&caEndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
caCustomEndpoint.setEnabled(
caEndpointMode.volatileValue() == caEndpointMode.indexForDisplay("Custom"));
});
connect(&qrEndpointMode, &Utils::BaseAspect::volatileValueChanged, this, [this]() {
qrCustomEndpoint.setEnabled(
qrEndpointMode.volatileValue() == qrEndpointMode.indexForDisplay("Custom"));
});
connect(&ccShowTemplateInfo, &ButtonAspect::clicked, this, [this]() {
showTemplateInfoDialog(ccTemplateDescription, ccTemplate.value());
});
@@ -733,17 +678,13 @@ void GeneralSettings::resetPageToDefaults()
resetAspect(ccPreset1Model);
resetAspect(ccPreset1Template);
resetAspect(ccPreset1Url);
resetAspect(ccEndpointMode);
resetAspect(ccCustomEndpoint);
resetAspect(ccPreset1EndpointMode);
resetAspect(ccPreset1CustomEndpoint);
resetAspect(caEndpointMode);
resetAspect(caCustomEndpoint);
resetAspect(qrProvider);
resetAspect(qrModel);
resetAspect(qrTemplate);
resetAspect(qrUrl);
resetAspect(qrEndpointMode);
resetAspect(qrCustomEndpoint);
writeSettings();
}
@@ -773,7 +714,6 @@ void GeneralSettings::onSaveConfiguration(const QString &prefix)
config.model = ccModel.value();
config.templateName = ccTemplate.value();
config.url = ccUrl.value();
config.endpointMode = ccEndpointMode.stringValue();
config.customEndpoint = ccCustomEndpoint.value();
config.type = ConfigurationType::CodeCompletion;
} else if (prefix == "ca") {
@@ -781,7 +721,6 @@ void GeneralSettings::onSaveConfiguration(const QString &prefix)
config.model = caModel.value();
config.templateName = caTemplate.value();
config.url = caUrl.value();
config.endpointMode = caEndpointMode.stringValue();
config.customEndpoint = caCustomEndpoint.value();
config.type = ConfigurationType::Chat;
} else if (prefix == "qr") {
@@ -789,7 +728,6 @@ void GeneralSettings::onSaveConfiguration(const QString &prefix)
config.model = qrModel.value();
config.templateName = qrTemplate.value();
config.url = qrUrl.value();
config.endpointMode = qrEndpointMode.stringValue();
config.customEndpoint = qrCustomEndpoint.value();
config.type = ConfigurationType::QuickRefactor;
}
@@ -926,21 +864,18 @@ void GeneralSettings::onLoadConfiguration(const QString &prefix)
ccModel.setValue(config.model);
ccTemplate.setValue(config.templateName);
ccUrl.setValue(config.url);
ccEndpointMode.setValue(ccEndpointMode.indexForDisplay(config.endpointMode));
ccCustomEndpoint.setValue(config.customEndpoint);
} else if (prefix == "ca") {
caProvider.setValue(config.provider);
caModel.setValue(config.model);
caTemplate.setValue(config.templateName);
caUrl.setValue(config.url);
caEndpointMode.setValue(caEndpointMode.indexForDisplay(config.endpointMode));
caCustomEndpoint.setValue(config.customEndpoint);
} else if (prefix == "qr") {
qrProvider.setValue(config.provider);
qrModel.setValue(config.model);
qrTemplate.setValue(config.templateName);
qrUrl.setValue(config.url);
qrEndpointMode.setValue(qrEndpointMode.indexForDisplay(config.endpointMode));
qrCustomEndpoint.setValue(config.customEndpoint);
}
@@ -1008,21 +943,18 @@ void GeneralSettings::applyPresetConfiguration(int index, ConfigurationType type
ccModel.setValue(config.model);
ccTemplate.setValue(config.templateName);
ccUrl.setValue(config.url);
ccEndpointMode.setValue(ccEndpointMode.indexForDisplay(config.endpointMode));
ccCustomEndpoint.setValue(config.customEndpoint);
} else if (type == ConfigurationType::Chat) {
caProvider.setValue(config.provider);
caModel.setValue(config.model);
caTemplate.setValue(config.templateName);
caUrl.setValue(config.url);
caEndpointMode.setValue(caEndpointMode.indexForDisplay(config.endpointMode));
caCustomEndpoint.setValue(config.customEndpoint);
} else if (type == ConfigurationType::QuickRefactor) {
qrProvider.setValue(config.provider);
qrModel.setValue(config.model);
qrTemplate.setValue(config.templateName);
qrUrl.setValue(config.url);
qrEndpointMode.setValue(qrEndpointMode.indexForDisplay(config.endpointMode));
qrCustomEndpoint.setValue(config.customEndpoint);
}

View File

@@ -62,7 +62,6 @@ public:
Utils::StringAspect ccUrl{this};
ButtonAspect ccSetUrl{this};
Utils::SelectionAspect ccEndpointMode{this};
Utils::StringAspect ccCustomEndpoint{this};
Utils::StringAspect ccStatus{this};
@@ -85,7 +84,6 @@ public:
Utils::StringAspect ccPreset1Url{this};
ButtonAspect ccPreset1SetUrl{this};
Utils::SelectionAspect ccPreset1EndpointMode{this};
Utils::StringAspect ccPreset1CustomEndpoint{this};
Utils::StringAspect ccPreset1Model{this};
@@ -110,7 +108,6 @@ public:
Utils::StringAspect caUrl{this};
ButtonAspect caSetUrl{this};
Utils::SelectionAspect caEndpointMode{this};
Utils::StringAspect caCustomEndpoint{this};
Utils::StringAspect caStatus{this};
@@ -138,7 +135,6 @@ public:
Utils::StringAspect qrUrl{this};
ButtonAspect qrSetUrl{this};
Utils::SelectionAspect qrEndpointMode{this};
Utils::StringAspect qrCustomEndpoint{this};
Utils::StringAspect qrStatus{this};

View File

@@ -35,7 +35,6 @@ const char CC_MODEL_HISTORY[] = "QodeAssist.ccModelHistory";
const char CC_TEMPLATE[] = "QodeAssist.ccTemplate";
const char CC_URL[] = "QodeAssist.ccUrl";
const char CC_URL_HISTORY[] = "QodeAssist.ccUrlHistory";
const char CC_ENDPOINT_MODE[] = "QodeAssist.ccEndpointMode";
const char CC_CUSTOM_ENDPOINT[] = "QodeAssist.ccCustomEndpoint";
const char CC_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.ccCustomEndpointHistory";
@@ -45,7 +44,6 @@ const char CA_MODEL_HISTORY[] = "QodeAssist.caModelHistory";
const char CA_TEMPLATE[] = "QodeAssist.caTemplate";
const char CA_URL[] = "QodeAssist.caUrl";
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory";
const char CA_ENDPOINT_MODE[] = "QodeAssist.caEndpointMode";
const char CA_CUSTOM_ENDPOINT[] = "QodeAssist.caCustomEndpoint";
const char CA_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caCustomEndpointHistory";
@@ -56,7 +54,6 @@ const char QR_MODEL_HISTORY[] = "QodeAssist.qrModelHistory";
const char QR_TEMPLATE[] = "QodeAssist.qrTemplate";
const char QR_URL[] = "QodeAssist.qrUrl";
const char QR_URL_HISTORY[] = "QodeAssist.qrUrlHistory";
const char QR_ENDPOINT_MODE[] = "QodeAssist.qrEndpointMode";
const char QR_CUSTOM_ENDPOINT[] = "QodeAssist.qrCustomEndpoint";
const char QR_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.qrCustomEndpointHistory";
@@ -68,9 +65,8 @@ const char CC_PRESET1_MODEL_HISTORY[] = "QodeAssist.ccPreset1ModelHistory";
const char CC_PRESET1_TEMPLATE[] = "QodeAssist.ccPreset1Template";
const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url";
const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory";
const char CC_PRESET1_ENDPOINT_MODE[] = "QodeAssist.caPreset1EndpointMode";
const char CC_PRESET1_CUSTOM_ENDPOINT[] = "QodeAssist.caPreset1CustomEndpointHistory";
const char CC_PRESET1_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caPreset1CustomEndpointHistory";
const char CC_PRESET1_CUSTOM_ENDPOINT[] = "QodeAssist.ccPreset1CustomEndpoint";
const char CC_PRESET1_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.ccPreset1CustomEndpointHistory";
// settings
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
@@ -93,7 +89,6 @@ const char CC_IGNORE_WHITESPACE_IN_CHAR_COUNT[] = "QodeAssist.ccIgnoreWhitespace
const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
const char CC_MULTILINE_COMPLETION[] = "QodeAssist.ccMultilineCompletion";
const char CC_MODEL_OUTPUT_HANDLER[] = "QodeAssist.ccModelOutputHandler";
const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate";
const char CA_AUTO_APPLY_FILE_EDITS[] = "QodeAssist.caAutoApplyFileEdits";
const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold";
const char CA_LINK_OPEN_FILES[] = "QodeAssist.caLinkOpenFiles";
@@ -127,7 +122,6 @@ const char QODE_ASSIST_CHAT_ASSISTANT_SETTINGS_PAGE_ID[]
const char QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID[]
= "QodeAssist.4QuickRefactorSettingsPageId";
const char QODE_ASSIST_TOOLS_SETTINGS_PAGE_ID[] = "QodeAssist.5ToolsSettingsPageId";
const char QODE_ASSIST_CUSTOM_PROMPT_SETTINGS_PAGE_ID[] = "QodeAssist.6CustomPromptSettingsPageId";
const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category";
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "QodeAssist";

View File

@@ -44,7 +44,7 @@ inline const char *ENABLE_CHECK_UPDATE_ON_START
inline const char *ENABLE_CHAT = QT_TRANSLATE_NOOP(
"QtC::QodeAssist",
"Enable Chat(If you have performance issues try disabling this, need restart QtC)");
inline const char *ENDPOINT_MODE = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Endpoint Mode:");
inline const char *CUSTOM_ENDPOINT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Custom endpoint:");
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");

1
sources/external/llmqore vendored Submodule

View File

@@ -28,6 +28,7 @@ class CodeLlamaFim : public PluginLLMCore::PromptTemplate
public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "CodeLlama FIM"; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override
{
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";

View File

@@ -28,6 +28,7 @@ class CodeLlamaQMLFim : public PluginLLMCore::PromptTemplate
public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "CodeLlama QML FIM"; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override
{
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"

View File

@@ -1,90 +0,0 @@
/*
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "llmcore/PromptTemplate.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include "logger/Logger.hpp"
#include "settings/CustomPromptSettings.hpp"
namespace QodeAssist::Templates {
class CustomTemplate : public LLMCore::PromptTemplate
{
public:
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
QString name() const override { return "Custom FIM Template"; }
QString promptTemplate() const override
{
return Settings::customPromptSettings().customJsonTemplate();
}
QStringList stopWords() const override { return QStringList(); }
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
{
QJsonDocument doc = QJsonDocument::fromJson(promptTemplate().toUtf8());
if (doc.isNull() || !doc.isObject()) {
LOG_MESSAGE(QString("Invalid JSON template in settings"));
return;
}
QJsonObject templateObj = doc.object();
QJsonObject processedObj = processJsonTemplate(templateObj, context);
for (auto it = processedObj.begin(); it != processedObj.end(); ++it) {
request[it.key()] = it.value();
}
}
QString description() const override { return promptTemplate(); }
private:
QJsonValue processJsonValue(const QJsonValue &value, const LLMCore::ContextData &context) const
{
if (value.isString()) {
QString str = value.toString();
str.replace("{{QODE_PREFIX}}", context.prefix);
str.replace("{{QODE_SUFFIX}}", context.suffix);
return str;
} else if (value.isObject()) {
return processJsonTemplate(value.toObject(), context);
} else if (value.isArray()) {
QJsonArray newArray;
for (const QJsonValue &arrayValue : value.toArray()) {
newArray.append(processJsonValue(arrayValue, context));
}
return newArray;
}
return value;
}
QJsonObject processJsonTemplate(
const QJsonObject &templateObj, const LLMCore::ContextData &context) const
{
QJsonObject result;
for (auto it = templateObj.begin(); it != templateObj.end(); ++it) {
result[it.key()] = processJsonValue(it.value(), context);
}
return result;
}
};
} // namespace QodeAssist::Templates

View File

@@ -23,17 +23,17 @@
namespace QodeAssist::Templates {
class DeepSeekCoderFim : public LLMCore::PromptTemplate
class DeepSeekCoderFim : public LLMQore::PromptTemplate
{
public:
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
LLMQore::TemplateType type() const override { return LLMQore::TemplateType::Fim; }
QString name() const override { return "DeepSeekCoder FIM"; }
QString promptTemplate() const override
{
return "<fim▁begin>%1<fim▁hole>%2<fim▁end>";
}
QStringList stopWords() const override { return QStringList(); }
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
void prepareRequest(QJsonObject &request, const LLMQore::ContextData &context) const override
{
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
request["prompt"] = formattedPrompt;

View File

@@ -30,6 +30,7 @@ class LlamaCppFim : public PluginLLMCore::PromptTemplate
public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "llama.cpp FIM"; }
QString endpoint() const override { return QStringLiteral("/infill"); }
QStringList stopWords() const override { return {}; }
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override

View File

@@ -30,6 +30,7 @@ class MistralAIFim : public PluginLLMCore::PromptTemplate
public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "Mistral AI FIM"; }
QString endpoint() const override { return QStringLiteral("/v1/fim/completions"); }
QStringList stopWords() const override { return QStringList(); }
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
{

View File

@@ -30,6 +30,7 @@ class OllamaFim : public PluginLLMCore::PromptTemplate
public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "Ollama FIM"; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override { return QStringList() << "<EOT>"; }
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
{

View File

@@ -29,6 +29,7 @@ class Qwen25CoderFIM : public PluginLLMCore::PromptTemplate
public:
QString name() const override { return "Qwen2.5 Coder FIM"; }
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override { return QStringList() << "<|endoftext|>" << "<|EOT|>"; }
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
{

View File

@@ -28,6 +28,7 @@ class StarCoder2Fim : public PluginLLMCore::PromptTemplate
public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "StarCoder2 FIM"; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override
{
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"

View File

@@ -30,8 +30,6 @@
#include "templates/OpenAI.hpp"
#include "templates/OpenAICompatible.hpp"
#include "templates/OpenAIResponses.hpp"
// #include "templates/CustomFimTemplate.hpp"
// #include "templates/DeepSeekCoderFim.hpp"
#include "templates/GoogleAI.hpp"
#include "templates/Llama2.hpp"
#include "templates/Llama3.hpp"
@@ -58,8 +56,6 @@ inline void registerTemplates()
templateManager.registerTemplate<Llama2>();
templateManager.registerTemplate<Llama3>();
templateManager.registerTemplate<StarCoder2Fim>();
// templateManager.registerTemplate<DeepSeekCoderFim>();
// templateManager.registerTemplate<CustomTemplate>();
templateManager.registerTemplate<Qwen25CoderFIM>();
templateManager.registerTemplate<Qwen3CoderFIM>();
templateManager.registerTemplate<OpenAICompatible>();

View File

@@ -18,7 +18,7 @@ target_link_libraries(QodeAssistTest PRIVATE
QtCreator::LanguageClient
Context
PluginLLMCore
LLMCore
LLMQore
)
target_compile_definitions(QodeAssistTest PRIVATE CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

View File

@@ -38,7 +38,7 @@ void PrintTo(const ContextData &data, std::ostream *os)
} // namespace QodeAssist::PluginLLMCore
using namespace QodeAssist::Context;
using namespace QodeAssist::LLMCore;
using namespace QodeAssist::PluginLLMCore;
using namespace QodeAssist::Settings;
class DocumentContextReaderTest : public QObject, public testing::Test

View File

@@ -1,209 +0,0 @@
/*
* Copyright (C) 2025 Povilas Kanapickas
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSignalSpy>
#include "LLMClientInterface.hpp"
#include "MockDocumentReader.hpp"
#include "MockRequestHandler.hpp"
#include "llmcore/IPromptProvider.hpp"
#include "llmcore/IProviderRegistry.hpp"
#include "logger/EmptyRequestPerformanceLogger.hpp"
#include "settings/CodeCompletionSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include "templates/Templates.hpp"
#include <coreplugin/editormanager/documentmodel.h>
using namespace testing;
namespace QodeAssist {
class MockPromptProvider : public LLMCore::IPromptProvider
{
public:
MOCK_METHOD(LLMCore::PromptTemplate *, getTemplateByName, (const QString &), (const override));
MOCK_METHOD(QStringList, templatesNames, (), (const override));
MOCK_METHOD(QStringList, getTemplatesForProvider, (LLMCore::ProviderID id), (const override));
};
class MockProviderRegistry : public LLMCore::IProviderRegistry
{
public:
MOCK_METHOD(LLMCore::Provider *, getProviderByName, (const QString &), (override));
MOCK_METHOD(QStringList, providersNames, (), (const override));
};
class MockProvider : public LLMCore::Provider
{
public:
QString name() const override { return "mock_provider"; }
QString url() const override { return "https://mock_url"; }
QString completionEndpoint() const override { return "/v1/completions"; }
QString chatEndpoint() const override { return "/v1/chat/completions"; }
void prepareRequest(
QJsonObject &request,
LLMCore::PromptTemplate *promptTemplate,
LLMCore::ContextData context,
LLMCore::RequestType requestType,
bool isToolsEnabled,
bool isThinkingEnabled) override
{
promptTemplate->prepareRequest(request, context);
}
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override
{
return true;
}
QFuture<QList<QString>> getInstalledModels(const QString &) override
{
return QtFuture::makeReadyFuture(QList<QString>{});
}
LLMCore::ProviderID providerID() const override { return LLMCore::ProviderID::OpenAI; }
};
class LLMClientInterfaceTest : public Test
{
protected:
void SetUp() override
{
Core::DocumentModel::init();
m_provider = std::make_unique<MockProvider>();
m_fimTemplate = std::make_unique<Templates::CodeLlamaQMLFim>();
m_chatTemplate = std::make_unique<Templates::Claude>();
ON_CALL(m_providerRegistry, getProviderByName(_)).WillByDefault(Return(m_provider.get()));
ON_CALL(m_promptProvider, getTemplateByName(_)).WillByDefault(Return(m_fimTemplate.get()));
EXPECT_CALL(m_providerRegistry, getProviderByName(_)).Times(testing::AnyNumber());
EXPECT_CALL(m_promptProvider, getTemplateByName(_)).Times(testing::AnyNumber());
m_generalSettings.ccProvider.setValue("mock_provider");
m_generalSettings.ccModel.setValue("mock_model");
m_generalSettings.ccTemplate.setValue("mock_template");
m_generalSettings.ccUrl.setValue("http://localhost:8000");
m_completeSettings.systemPromptForNonFimModels.setValue("system prompt non fim");
m_completeSettings.systemPrompt.setValue("system prompt");
m_completeSettings.userMessageTemplateForCC.setValue(
"user message template prefix:\n${prefix}\nsuffix:\n${suffix}\n");
m_client = std::make_unique<LLMClientInterface>(
m_generalSettings,
m_completeSettings,
m_providerRegistry,
&m_promptProvider,
m_documentReader,
m_performanceLogger);
}
void TearDown() override { Core::DocumentModel::destroy(); }
QJsonObject createInitializeRequest()
{
QJsonObject request;
request["jsonrpc"] = "2.0";
request["id"] = "init-1";
request["method"] = "initialize";
return request;
}
QString buildTestFilePath() { return QString(CMAKE_CURRENT_SOURCE_DIR) + "/test_file.py"; }
QJsonObject createCompletionRequest()
{
QJsonObject position;
position["line"] = 2;
position["character"] = 5;
QJsonObject doc;
// change next line to link to test_file.py in current directory of the cmake project
doc["uri"] = "file://" + buildTestFilePath();
doc["position"] = position;
QJsonObject params;
params["doc"] = doc;
QJsonObject request;
request["jsonrpc"] = "2.0";
request["id"] = "completion-1";
request["method"] = "getCompletionsCycling";
request["params"] = params;
return request;
}
QJsonObject createCancelRequest(const QString &idToCancel)
{
QJsonObject params;
params["id"] = idToCancel;
QJsonObject request;
request["jsonrpc"] = "2.0";
request["id"] = "cancel-1";
request["method"] = "$/cancelRequest";
request["params"] = params;
return request;
}
Settings::GeneralSettings m_generalSettings;
Settings::CodeCompletionSettings m_completeSettings;
MockProviderRegistry m_providerRegistry;
MockPromptProvider m_promptProvider;
MockDocumentReader m_documentReader;
EmptyRequestPerformanceLogger m_performanceLogger;
std::unique_ptr<LLMClientInterface> m_client;
std::unique_ptr<MockProvider> m_provider;
std::unique_ptr<LLMCore::PromptTemplate> m_fimTemplate;
std::unique_ptr<LLMCore::PromptTemplate> m_chatTemplate;
};
TEST_F(LLMClientInterfaceTest, initialize)
{
QSignalSpy spy(m_client.get(), &LanguageClient::BaseClientInterface::messageReceived);
QJsonObject request = createInitializeRequest();
m_client->sendData(QJsonDocument(request).toJson());
ASSERT_EQ(spy.count(), 1);
auto message = spy.takeFirst().at(0).value<LanguageServerProtocol::JsonRpcMessage>();
QJsonObject response = message.toJsonObject();
EXPECT_EQ(response["id"].toString(), "init-1");
EXPECT_TRUE(response.contains("result"));
EXPECT_TRUE(response["result"].toObject().contains("capabilities"));
EXPECT_TRUE(response["result"].toObject().contains("serverInfo"));
}
TEST_F(LLMClientInterfaceTest, ServerDeviceTemplate)
{
EXPECT_EQ(m_client->serverDeviceTemplate().toFSPathString(), "QodeAssist");
}
} // namespace QodeAssist

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <llmcore/RequestHandlerBase.hpp>
namespace QodeAssist::LLMCore {
class MockRequestHandler : public RequestHandlerBase
{
public:
explicit MockRequestHandler(QObject *parent = nullptr)
: RequestHandlerBase(parent)
, m_fakeCompletion("")
{}
void setFakeCompletion(const QString &completion) { m_fakeCompletion = completion; }
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request) override
{
m_receivedRequests.append(config);
emit completionReceived(m_fakeCompletion, request, true);
QString requestId = request["id"].toString();
emit requestFinished(requestId, true, QString());
}
bool cancelRequest(const QString &id) override
{
emit requestCancelled(id);
return true;
}
const QVector<LLMConfig> &receivedRequests() const { return m_receivedRequests; }
private:
QString m_fakeCompletion;
QVector<LLMConfig> m_receivedRequests;
};
} // namespace QodeAssist::LLMCore

View File

@@ -59,16 +59,16 @@ std::ostream &operator<<(std::ostream &out, const std::optional<T> &value)
return out;
}
namespace QodeAssist::LLMCore {
namespace QodeAssist::PluginLLMCore {
inline std::ostream &operator<<(std::ostream &out, const PluginLLMCore::Message &value)
inline std::ostream &operator<<(std::ostream &out, const Message &value)
{
out << "Message{"
<< "role=" << value.role << "content=" << value.content << "}";
return out;
}
inline std::ostream &operator<<(std::ostream &out, const PluginLLMCore::ContextData &value)
inline std::ostream &operator<<(std::ostream &out, const ContextData &value)
{
out << "ContextData{"
<< "\n systemPrompt=" << value.systemPrompt << "\n prefix=" << value.prefix
@@ -77,4 +77,4 @@ inline std::ostream &operator<<(std::ostream &out, const PluginLLMCore::ContextD
return out;
}
} // namespace QodeAssist::LLMCore
} // namespace QodeAssist::PluginLLMCore

View File

@@ -99,23 +99,23 @@ QJsonObject BuildProjectTool::parametersSchema() const
return definition;
}
QFuture<QString> BuildProjectTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> BuildProjectTool::executeAsync(const QJsonObject &input)
{
auto *project = ProjectExplorer::ProjectManager::startupProject();
if (!project) {
return QtFuture::makeReadyFuture(
QString("Error: No active project found. Please open a project in Qt Creator."));
LLMQore::ToolResult::error("Error: No active project found. Please open a project in Qt Creator."));
}
if (ProjectExplorer::BuildManager::isBuilding(project)) {
return QtFuture::makeReadyFuture(
QString("Error: Build is already in progress. Please wait for it to complete."));
LLMQore::ToolResult::error("Error: Build is already in progress. Please wait for it to complete."));
}
if (m_activeBuilds.contains(project)) {
return QtFuture::makeReadyFuture(
QString("Error: Build is already being tracked for project '%1'.")
.arg(project->displayName()));
LLMQore::ToolResult::error(QString("Error: Build is already being tracked for project '%1'.")
.arg(project->displayName())));
}
bool rebuild = input.value("rebuild").toBool(false);
@@ -126,7 +126,7 @@ QFuture<QString> BuildProjectTool::executeAsync(const QJsonObject &input)
.arg(project->displayName())
.arg(runAfterBuild ? QString(" (run after build)") : QString()));
auto promise = QSharedPointer<QPromise<QString>>::create();
auto promise = QSharedPointer<QPromise<LLMQore::ToolResult>>::create();
promise->start();
BuildInfo buildInfo;
@@ -187,7 +187,7 @@ void BuildProjectTool::onBuildQueueFinished(bool success)
}
if (info.promise) {
info.promise->addResult(result);
info.promise->addResult(LLMQore::ToolResult::text(result));
info.promise->finish();
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <QHash>
#include <QObject>
#include <QPointer>
@@ -34,7 +34,7 @@ namespace QodeAssist::Tools {
struct BuildInfo
{
QSharedPointer<QPromise<QString>> promise;
QSharedPointer<QPromise<LLMQore::ToolResult>> promise;
QPointer<ProjectExplorer::Project> project;
QString projectName;
bool isRebuild = false;
@@ -42,7 +42,7 @@ struct BuildInfo
QMetaObject::Connection buildFinishedConnection;
};
class BuildProjectTool : public ::LLMCore::BaseTool
class BuildProjectTool : public ::LLMQore::BaseTool
{
Q_OBJECT
public:
@@ -54,7 +54,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
private slots:
void onBuildQueueFinished(bool success);

View File

@@ -18,7 +18,8 @@
*/
#include "CreateNewFileTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <context/ProjectUtils.hpp>
#include <logger/Logger.hpp>
@@ -73,24 +74,24 @@ QJsonObject CreateNewFileTool::parametersSchema() const
return definition;
}
QFuture<QString> CreateNewFileTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> CreateNewFileTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([this, input]() -> QString {
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
QString filePath = input["filepath"].toString();
if (filePath.isEmpty()) {
throw ToolInvalidArgument("Error: 'filepath' parameter is required");
throw LLMQore::ToolInvalidArgument("Error: 'filepath' parameter is required");
}
QFileInfo fileInfo(filePath);
QString absolutePath = fileInfo.absoluteFilePath();
bool isInProject = Context::ProjectUtils::isFileInProject(absolutePath);
if (!isInProject) {
const auto &settings = Settings::toolsSettings();
if (!settings.allowAccessOutsideProject()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("Error: File path '%1' is not within the current project. "
"Enable 'Allow file access outside project' in settings to create files outside project scope.")
.arg(absolutePath));
@@ -99,14 +100,14 @@ QFuture<QString> CreateNewFileTool::executeAsync(const QJsonObject &input)
}
if (fileInfo.exists()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("Error: File already exists at path '%1'").arg(filePath));
}
QDir dir = fileInfo.absoluteDir();
if (!dir.exists()) {
if (!dir.mkpath(".")) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("Error: Could not create directory: '%1'").arg(dir.absolutePath()));
}
LOG_MESSAGE(QString("Created directory path: %1").arg(dir.absolutePath()));
@@ -114,7 +115,7 @@ QFuture<QString> CreateNewFileTool::executeAsync(const QJsonObject &input)
QFile file(absolutePath);
if (!file.open(QIODevice::WriteOnly)) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("Error: Could not create file '%1': %2").arg(absolutePath, file.errorString()));
}
@@ -122,7 +123,7 @@ QFuture<QString> CreateNewFileTool::executeAsync(const QJsonObject &input)
LOG_MESSAGE(QString("Successfully created new file: %1").arg(absolutePath));
return QString("Successfully created new file: %1").arg(absolutePath);
return LLMQore::ToolResult::text(QString("Successfully created new file: %1").arg(absolutePath));
});
}

View File

@@ -19,11 +19,11 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
namespace QodeAssist::Tools {
class CreateNewFileTool : public ::LLMCore::BaseTool
class CreateNewFileTool : public ::LLMQore::BaseTool
{
Q_OBJECT
public:
@@ -34,7 +34,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
};
} // namespace QodeAssist::Tools

View File

@@ -18,7 +18,8 @@
*/
#include "EditFileTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <context/ChangesManager.h>
#include <context/ProjectUtils.hpp>
@@ -107,20 +108,20 @@ QJsonObject EditFileTool::parametersSchema() const
return definition;
}
QFuture<QString> EditFileTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> EditFileTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([this, input]() -> QString {
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
QString filename = input["filename"].toString().trimmed();
QString oldContent = input["old_content"].toString();
QString newContent = input["new_content"].toString();
QString requestId = input["_request_id"].toString();
if (filename.isEmpty()) {
throw ToolInvalidArgument("'filename' parameter is required and cannot be empty");
throw LLMQore::ToolInvalidArgument("'filename' parameter is required and cannot be empty");
}
if (newContent.isEmpty()) {
throw ToolInvalidArgument("'new_content' parameter is required and cannot be empty");
throw LLMQore::ToolInvalidArgument("'new_content' parameter is required and cannot be empty");
}
@@ -132,7 +133,7 @@ QFuture<QString> EditFileTool::executeAsync(const QJsonObject &input)
} else {
QString projectRoot = Context::ProjectUtils::getProjectRoot();
if (projectRoot.isEmpty()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("Cannot resolve relative path '%1': no project is open. "
"Please provide an absolute path or open a project.")
.arg(filename));
@@ -145,12 +146,12 @@ QFuture<QString> EditFileTool::executeAsync(const QJsonObject &input)
QFile file(filePath);
if (!file.exists()) {
throw ToolRuntimeError(QString("File does not exist: %1").arg(filePath));
throw LLMQore::ToolRuntimeError(QString("File does not exist: %1").arg(filePath));
}
QFileInfo finalFileInfo(filePath);
if (!finalFileInfo.isWritable()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("File is not writable (read-only or permission denied): %1").arg(filePath));
}
@@ -158,7 +159,7 @@ QFuture<QString> EditFileTool::executeAsync(const QJsonObject &input)
if (!isInProject) {
const auto &settings = Settings::toolsSettings();
if (!settings.allowAccessOutsideProject()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
QString("File path '%1' is not within the current project. "
"Enable 'Allow file access outside project' in settings to edit files outside the project.")
.arg(filePath));
@@ -220,7 +221,7 @@ QFuture<QString> EditFileTool::executeAsync(const QJsonObject &input)
QString resultStr = "QODEASSIST_FILE_EDIT:"
+ QString::fromUtf8(QJsonDocument(result).toJson(QJsonDocument::Compact));
return resultStr;
return LLMQore::ToolResult::text(resultStr);
});
}

View File

@@ -19,11 +19,11 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
namespace QodeAssist::Tools {
class EditFileTool : public ::LLMCore::BaseTool
class EditFileTool : public ::LLMQore::BaseTool
{
Q_OBJECT
public:
@@ -34,7 +34,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
};
} // namespace QodeAssist::Tools

View File

@@ -80,30 +80,32 @@ QJsonObject ExecuteTerminalCommandTool::parametersSchema() const
return definition;
}
QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &input)
{
using LLMQore::ToolResult;
const QString command = input.value("command").toString().trimmed();
const QString args = input.value("args").toString().trimmed();
if (command.isEmpty()) {
LOG_MESSAGE("ExecuteTerminalCommandTool: Command is empty");
return QtFuture::makeReadyFuture(QString("Error: Command parameter is required."));
return QtFuture::makeReadyFuture(ToolResult::error("Error: Command parameter is required."));
}
if (command.length() > MAX_COMMAND_LENGTH) {
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command too long (%1 chars)")
.arg(command.length()));
return QtFuture::makeReadyFuture(
QString("Error: Command exceeds maximum length of %1 characters.")
.arg(MAX_COMMAND_LENGTH));
ToolResult::error(QString("Error: Command exceeds maximum length of %1 characters.")
.arg(MAX_COMMAND_LENGTH)));
}
if (args.length() > MAX_ARGS_LENGTH) {
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Arguments too long (%1 chars)")
.arg(args.length()));
return QtFuture::makeReadyFuture(
QString("Error: Arguments exceed maximum length of %1 characters.")
.arg(MAX_ARGS_LENGTH));
ToolResult::error(QString("Error: Arguments exceed maximum length of %1 characters.")
.arg(MAX_ARGS_LENGTH)));
}
if (!isCommandAllowed(command)) {
@@ -112,9 +114,9 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
const QStringList allowed = getAllowedCommands();
const QString allowedList = allowed.isEmpty() ? "none" : allowed.join(", ");
return QtFuture::makeReadyFuture(
QString("Error: Command '%1' is not in the allowed list. Allowed commands: %2")
ToolResult::error(QString("Error: Command '%1' is not in the allowed list. Allowed commands: %2")
.arg(command)
.arg(allowedList));
.arg(allowedList)));
}
if (!isCommandSafe(command)) {
@@ -127,18 +129,18 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
const QString allowedChars = "alphanumeric characters, hyphens, underscores, dots, and slashes";
#endif
return QtFuture::makeReadyFuture(
QString("Error: Command '%1' contains potentially dangerous characters. "
ToolResult::error(QString("Error: Command '%1' contains potentially dangerous characters. "
"Only %2 are allowed.")
.arg(command)
.arg(allowedChars));
.arg(allowedChars)));
}
if (!args.isEmpty() && !areArgumentsSafe(args)) {
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Arguments contain unsafe patterns: '%1'")
.arg(args));
return QtFuture::makeReadyFuture(
QString("Error: Arguments contain potentially dangerous patterns (command chaining, "
"redirection, or pipe operators)."));
ToolResult::error(QString("Error: Arguments contain potentially dangerous patterns (command chaining, "
"redirection, or pipe operators).")));
}
auto *project = ProjectExplorer::ProjectManager::startupProject();
@@ -159,8 +161,8 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
QString("ExecuteTerminalCommandTool: Working directory '%1' is not accessible")
.arg(workingDir));
return QtFuture::makeReadyFuture(
QString("Error: Working directory '%1' does not exist or is not accessible.")
.arg(workingDir));
ToolResult::error(QString("Error: Working directory '%1' does not exist or is not accessible.")
.arg(workingDir)));
}
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Executing command '%1' with args '%2' in '%3'")
@@ -168,8 +170,8 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
.arg(args.isEmpty() ? "(no args)" : args)
.arg(workingDir));
auto promise = QSharedPointer<QPromise<QString>>::create();
QFuture<QString> future = promise->future();
auto promise = QSharedPointer<QPromise<ToolResult>>::create();
QFuture<ToolResult> future = promise->future();
promise->start();
auto resolved = std::make_shared<std::atomic<bool>>(false);
@@ -206,11 +208,11 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
process->deleteLater();
});
promise->addResult(QString("Error: Command '%1 %2' timed out after %3 seconds. "
promise->addResult(ToolResult::error(QString("Error: Command '%1 %2' timed out after %3 seconds. "
"The process has been terminated.")
.arg(command)
.arg(args.isEmpty() ? "" : args)
.arg(timeoutMs / 1000));
.arg(timeoutMs / 1000)));
promise->finish();
timeoutTimer->deleteLater();
});
@@ -241,21 +243,21 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
"successfully (output size: %2 bytes)")
.arg(fullCommand)
.arg(outputSize));
promise->addResult(
promise->addResult(ToolResult::text(
QString("Command '%1' executed successfully.\n\nOutput:\n%2")
.arg(fullCommand)
.arg(output.isEmpty() ? "(no output)" : output));
.arg(output.isEmpty() ? "(no output)" : output)));
} else {
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' failed with "
"exit code %2 (output size: %3 bytes)")
.arg(fullCommand)
.arg(exitCode)
.arg(outputSize));
promise->addResult(
promise->addResult(ToolResult::error(
QString("Command '%1' failed with exit code %2.\n\nOutput:\n%3")
.arg(fullCommand)
.arg(exitCode)
.arg(output.isEmpty() ? "(no output)" : output));
.arg(output.isEmpty() ? "(no output)" : output)));
}
} else {
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' crashed or was "
@@ -263,11 +265,11 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
.arg(fullCommand)
.arg(outputSize));
const QString error = process->errorString();
promise->addResult(
promise->addResult(ToolResult::error(
QString("Command '%1' crashed or was terminated.\n\nError: %2\n\nOutput:\n%3")
.arg(fullCommand)
.arg(error)
.arg(output.isEmpty() ? "(no output)" : output));
.arg(output.isEmpty() ? "(no output)" : output)));
}
promise->finish();
@@ -317,7 +319,7 @@ QFuture<QString> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &inp
break;
}
promise->addResult(QString("Error: %1").arg(errorMessage));
promise->addResult(ToolResult::error(QString("Error: %1").arg(errorMessage)));
promise->finish();
process->deleteLater();
});

View File

@@ -19,12 +19,12 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <QObject>
namespace QodeAssist::Tools {
class ExecuteTerminalCommandTool : public ::LLMCore::BaseTool
class ExecuteTerminalCommandTool : public ::LLMQore::BaseTool
{
Q_OBJECT
public:
@@ -35,7 +35,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
private:
bool isCommandAllowed(const QString &command) const;

View File

@@ -18,7 +18,8 @@
*/
#include "FindAndReadFileTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <logger/Logger.hpp>
#include <QJsonArray>
@@ -71,12 +72,12 @@ QJsonObject FindAndReadFileTool::parametersSchema() const
return definition;
}
QFuture<QString> FindAndReadFileTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> FindAndReadFileTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([this, input]() -> QString {
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
QString query = input["query"].toString().trimmed();
if (query.isEmpty()) {
throw ToolInvalidArgument("Query parameter is required");
throw LLMQore::ToolInvalidArgument("Query parameter is required");
}
QString filePattern = input["file_pattern"].toString();
@@ -90,7 +91,7 @@ QFuture<QString> FindAndReadFileTool::executeAsync(const QJsonObject &input)
query, filePattern, 10, m_ignoreManager);
if (bestMatch.absolutePath.isEmpty()) {
return QString("No file found matching '%1'").arg(query);
return LLMQore::ToolResult::text(QString("No file found matching '%1'").arg(query));
}
if (readContent) {
@@ -100,7 +101,7 @@ QFuture<QString> FindAndReadFileTool::executeAsync(const QJsonObject &input)
}
}
return formatResult(bestMatch, readContent);
return LLMQore::ToolResult::text(formatResult(bestMatch, readContent));
});
}

View File

@@ -22,14 +22,14 @@
#include "FileSearchUtils.hpp"
#include <context/IgnoreManager.hpp>
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <QFuture>
#include <QJsonObject>
#include <QObject>
namespace QodeAssist::Tools {
class FindAndReadFileTool : public ::LLMCore::BaseTool
class FindAndReadFileTool : public ::LLMQore::BaseTool
{
Q_OBJECT
@@ -40,7 +40,7 @@ public:
QString displayName() const override;
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
private:
QString formatResult(const FileSearchUtils::FileMatch &match, bool readContent) const;

View File

@@ -155,16 +155,16 @@ QJsonObject GetIssuesListTool::parametersSchema() const
return definition;
}
QFuture<QString> GetIssuesListTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> GetIssuesListTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([input]() -> QString {
return QtConcurrent::run([input]() -> LLMQore::ToolResult {
QString severityFilter = input.value("severity").toString("all");
const auto tasks = IssuesTracker::instance().getTasks();
if (tasks.isEmpty()) {
return "No issues found in Qt Creator Issues panel.";
return LLMQore::ToolResult::text("No issues found in Qt Creator Issues panel.");
}
QStringList results;
@@ -235,7 +235,7 @@ QFuture<QString> GetIssuesListTool::executeAsync(const QJsonObject &input)
.arg(processedCount);
results.prepend(summary);
return results.join("\n\n");
return LLMQore::ToolResult::text(results.join("\n\n"));
});
}

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <projectexplorer/task.h>
#include <QList>
#include <QMutex>
@@ -46,7 +46,7 @@ private:
mutable QMutex m_mutex;
};
class GetIssuesListTool : public ::LLMCore::BaseTool
class GetIssuesListTool : public ::LLMQore::BaseTool
{
Q_OBJECT
public:
@@ -57,7 +57,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
};
} // namespace QodeAssist::Tools

View File

@@ -18,7 +18,8 @@
*/
#include "ListProjectFilesTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <logger/Logger.hpp>
#include <projectexplorer/project.h>
@@ -63,15 +64,15 @@ QJsonObject ListProjectFilesTool::parametersSchema() const
return definition;
}
QFuture<QString> ListProjectFilesTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> ListProjectFilesTool::executeAsync(const QJsonObject &input)
{
Q_UNUSED(input)
return QtConcurrent::run([this]() -> QString {
return QtConcurrent::run([this]() -> LLMQore::ToolResult {
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
if (projects.isEmpty()) {
QString error = "No projects found";
throw ToolRuntimeError(error);
throw LLMQore::ToolRuntimeError(error);
}
QString result;
@@ -123,7 +124,7 @@ QFuture<QString> ListProjectFilesTool::executeAsync(const QJsonObject &input)
result += "\n";
}
return result.trimmed();
return LLMQore::ToolResult::text(result.trimmed());
});
}

View File

@@ -19,13 +19,13 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <context/IgnoreManager.hpp>
namespace QodeAssist::Tools {
class ListProjectFilesTool : public ::LLMCore::BaseTool
class ListProjectFilesTool : public ::LLMQore::BaseTool
{
Q_OBJECT
public:
@@ -36,7 +36,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
private:
QString formatFileList(const QStringList &files) const;

View File

@@ -18,7 +18,8 @@
*/
#include "ProjectSearchTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <cplusplus/Overview.h>
#include <cplusplus/Scope.h>
@@ -97,17 +98,17 @@ QJsonObject ProjectSearchTool::parametersSchema() const
return definition;
}
QFuture<QString> ProjectSearchTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> ProjectSearchTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([this, input]() -> QString {
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
QString query = input["query"].toString().trimmed();
if (query.isEmpty()) {
throw ToolInvalidArgument("Query parameter is required");
throw LLMQore::ToolInvalidArgument("Query parameter is required");
}
QString searchTypeStr = input["search_type"].toString();
if (searchTypeStr != "text" && searchTypeStr != "symbol") {
throw ToolInvalidArgument("search_type must be 'text' or 'symbol'");
throw LLMQore::ToolInvalidArgument("search_type must be 'text' or 'symbol'");
}
SearchType searchType = (searchTypeStr == "symbol") ? SearchType::Symbol : SearchType::Text;
@@ -129,10 +130,10 @@ QFuture<QString> ProjectSearchTool::executeAsync(const QJsonObject &input)
}
if (results.isEmpty()) {
return QString("No matches found for '%1'").arg(query);
return LLMQore::ToolResult::text(QString("No matches found for '%1'").arg(query));
}
return formatResults(results, query);
return LLMQore::ToolResult::text(formatResults(results, query));
});
}

View File

@@ -20,14 +20,14 @@
#pragma once
#include <context/IgnoreManager.hpp>
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <QFuture>
#include <QJsonObject>
#include <QObject>
namespace QodeAssist::Tools {
class ProjectSearchTool : public ::LLMCore::BaseTool
class ProjectSearchTool : public ::LLMQore::BaseTool
{
Q_OBJECT
@@ -38,7 +38,7 @@ public:
QString displayName() const override;
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
private:
enum class SearchType { Text, Symbol };

View File

@@ -18,7 +18,8 @@
*/
#include "TodoTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <QJsonArray>
#include <QJsonObject>
@@ -100,9 +101,9 @@ QJsonObject TodoTool::parametersSchema() const
return definition;
}
QFuture<QString> TodoTool::executeAsync(const QJsonObject &input)
QFuture<LLMQore::ToolResult> TodoTool::executeAsync(const QJsonObject &input)
{
return QtConcurrent::run([this, input]() -> QString {
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
QMutexLocker sessionLocker(&m_mutex);
QString sessionId = m_currentSessionId.isEmpty() ? "current" : m_currentSessionId;
sessionLocker.unlock();
@@ -111,14 +112,14 @@ QFuture<QString> TodoTool::executeAsync(const QJsonObject &input)
if (operation == "add") {
if (!input.contains("tasks") || !input.value("tasks").isArray()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: 'tasks' parameter (array) is required for 'add' operation. "
"Example: {\"operation\": \"add\", \"tasks\": [\"Task 1\", \"Task 2\"]}"));
}
const QJsonArray tasksArray = input.value("tasks").toArray();
if (tasksArray.isEmpty()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: 'tasks' array cannot be empty. Provide at least one task."));
}
@@ -131,22 +132,22 @@ QFuture<QString> TodoTool::executeAsync(const QJsonObject &input)
}
if (tasks.isEmpty()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: All tasks in 'tasks' array are empty strings."));
}
return addTodos(sessionId, tasks);
return LLMQore::ToolResult::text(addTodos(sessionId, tasks));
} else if (operation == "complete") {
if (!input.contains("todo_ids") || !input.value("todo_ids").isArray()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: 'todo_ids' parameter (array) is required for 'complete' operation. "
"Example: {\"operation\": \"complete\", \"todo_ids\": [1, 2, 3]}"));
}
const QJsonArray idsArray = input.value("todo_ids").toArray();
if (idsArray.isEmpty()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: 'todo_ids' array cannot be empty. Provide at least one ID."));
}
@@ -159,18 +160,18 @@ QFuture<QString> TodoTool::executeAsync(const QJsonObject &input)
}
if (ids.isEmpty()) {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: All IDs in 'todo_ids' array are invalid. IDs must be positive "
"integers."));
}
return completeTodos(sessionId, ids);
return LLMQore::ToolResult::text(completeTodos(sessionId, ids));
} else if (operation == "list") {
return listTodos(sessionId);
return LLMQore::ToolResult::text(listTodos(sessionId));
} else {
throw ToolRuntimeError(
throw LLMQore::ToolRuntimeError(
tr("Error: Unknown operation '%1'. Valid operations: 'add', 'complete', 'list'")
.arg(operation));
}
@@ -215,7 +216,7 @@ QString TodoTool::completeTodos(const QString &sessionId, const QList<int> &todo
QMutexLocker locker(&m_mutex);
if (!m_sessionTodos.contains(sessionId)) {
throw ToolRuntimeError(tr("Error: No todos found in this session"));
throw LLMQore::ToolRuntimeError(tr("Error: No todos found in this session"));
}
auto &todos = m_sessionTodos[sessionId];

View File

@@ -19,7 +19,7 @@
#pragma once
#include <LLMCore/BaseTool.hpp>
#include <LLMQore/BaseTool.hpp>
#include <QHash>
#include <QMutex>
@@ -34,7 +34,7 @@ struct TodoItem
bool completed;
};
class TodoTool : public ::LLMCore::BaseTool
class TodoTool : public ::LLMQore::BaseTool
{
Q_OBJECT
@@ -46,7 +46,7 @@ public:
QString description() const override;
QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input = QJsonObject()) override;
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input = QJsonObject()) override;
void setCurrentSessionId(const QString &sessionId);
void clearSession(const QString &sessionId);

View File

@@ -1,142 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "ToolHandler.hpp"
#include "ToolExceptions.hpp"
#include <QJsonDocument>
#include <QTimer>
#include <QtConcurrent>
#include "logger/Logger.hpp"
namespace QodeAssist::Tools {
ToolHandler::ToolHandler(QObject *parent)
: QObject(parent)
{}
QFuture<QString> ToolHandler::executeToolAsync(
const QString &requestId,
const QString &toolId,
PluginLLMCore::BaseTool *tool,
const QJsonObject &input)
{
if (!tool) {
return QtConcurrent::run([]() -> QString { throw std::runtime_error("Tool is null"); });
}
auto execution = std::make_unique<ToolExecution>();
execution->requestId = requestId;
execution->toolId = toolId;
execution->toolName = tool->name();
execution->watcher = new QFutureWatcher<QString>(this);
connect(execution->watcher, &QFutureWatcher<QString>::finished, this, [this, toolId]() {
onToolExecutionFinished(toolId);
});
LOG_MESSAGE(QString("Starting tool execution: %1 (ID: %2)").arg(tool->name(), toolId));
auto future = tool->executeAsync(input);
execution->watcher->setFuture(future);
m_activeExecutions.insert(toolId, execution.release());
return future;
}
void ToolHandler::cleanupRequest(const QString &requestId)
{
auto it = m_activeExecutions.begin();
while (it != m_activeExecutions.end()) {
if (it.value()->requestId == requestId) {
auto execution = it.value();
LOG_MESSAGE(
QString("Canceling tool %1 for request %2").arg(execution->toolName, requestId));
if (execution->watcher) {
execution->watcher->cancel();
execution->watcher->deleteLater();
}
delete execution;
it = m_activeExecutions.erase(it);
} else {
++it;
}
}
}
void ToolHandler::onToolExecutionFinished(const QString &toolId)
{
if (!m_activeExecutions.contains(toolId)) {
return;
}
auto execution = m_activeExecutions.take(toolId);
try {
QString result = execution->watcher->result();
LOG_MESSAGE(QString("Tool %1 completed").arg(execution->toolName));
emit toolCompleted(execution->requestId, execution->toolId, result);
} catch (const ToolException &e) {
QString error = e.message();
if (error.isEmpty()) {
error = "Tool execution failed with empty error message";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const QException &e) {
QString error = QString::fromUtf8(e.what());
if (error.isEmpty()) {
error = "Tool execution failed (QException with empty message)";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const std::runtime_error &e) {
QString error = QString::fromStdString(e.what());
if (error.isEmpty()) {
error = "Unknown runtime error occurred";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const std::invalid_argument &e) {
QString error = QString::fromStdString(e.what());
if (error.isEmpty()) {
error = "Invalid argument provided";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const std::exception &e) {
QString error = QString::fromStdString(e.what());
if (error.isEmpty()) {
error = "Unknown exception occurred";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (...) {
QString error = "Unknown error occurred during tool execution";
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
}
execution->watcher->deleteLater();
delete execution;
}
} // namespace QodeAssist::Tools

View File

@@ -1,65 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFutureWatcher>
#include <QHash>
#include <QJsonObject>
#include <QObject>
#include <QString>
#include <pluginllmcore/BaseTool.hpp>
namespace QodeAssist::Tools {
class ToolHandler : public QObject
{
Q_OBJECT
public:
explicit ToolHandler(QObject *parent = nullptr);
QFuture<QString> executeToolAsync(
const QString &requestId,
const QString &toolId,
PluginLLMCore::BaseTool *tool,
const QJsonObject &input);
void cleanupRequest(const QString &requestId);
signals:
void toolCompleted(const QString &requestId, const QString &toolId, const QString &result);
void toolFailed(const QString &requestId, const QString &toolId, const QString &error);
private:
struct ToolExecution
{
QString requestId;
QString toolId;
QString toolName;
QFutureWatcher<QString> *watcher;
};
QHash<QString, ToolExecution *> m_activeExecutions; // toolId -> execution
void onToolExecutionFinished(const QString &toolId);
};
} // namespace QodeAssist::Tools

View File

@@ -1,187 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "ToolsFactory.hpp"
#include "logger/Logger.hpp"
#include <settings/GeneralSettings.hpp>
#include <settings/ToolsSettings.hpp>
#include <QJsonArray>
#include <QJsonObject>
#include "BuildProjectTool.hpp"
#include "CreateNewFileTool.hpp"
#include "EditFileTool.hpp"
#include "ExecuteTerminalCommandTool.hpp"
#include "FindAndReadFileTool.hpp"
#include "GetIssuesListTool.hpp"
#include "ListProjectFilesTool.hpp"
#include "ProjectSearchTool.hpp"
#include "TodoTool.hpp"
namespace QodeAssist::Tools {
ToolsFactory::ToolsFactory(QObject *parent)
: QObject(parent)
{
registerTools();
}
void ToolsFactory::registerTools()
{
registerTool(new ListProjectFilesTool(this));
registerTool(new GetIssuesListTool(this));
registerTool(new CreateNewFileTool(this));
registerTool(new EditFileTool(this));
registerTool(new BuildProjectTool(this));
registerTool(new ExecuteTerminalCommandTool(this));
registerTool(new ProjectSearchTool(this));
registerTool(new FindAndReadFileTool(this));
registerTool(new TodoTool(this));
LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size()));
}
void ToolsFactory::registerTool(PluginLLMCore::BaseTool *tool)
{
if (!tool) {
LOG_MESSAGE("Warning: Attempted to register null tool");
return;
}
const QString toolName = tool->name();
if (m_tools.contains(toolName)) {
LOG_MESSAGE(QString("Warning: Tool '%1' already registered, replacing").arg(toolName));
}
m_tools.insert(toolName, tool);
}
QList<PluginLLMCore::BaseTool *> ToolsFactory::getAvailableTools() const
{
return m_tools.values();
}
PluginLLMCore::BaseTool *ToolsFactory::getToolByName(const QString &name) const
{
return m_tools.value(name, nullptr);
}
QJsonArray ToolsFactory::getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format, PluginLLMCore::RunToolsFilter filter) const
{
QJsonArray toolsArray;
const auto &settings = Settings::toolsSettings();
for (auto it = m_tools.constBegin(); it != m_tools.constEnd(); ++it) {
if (!it.value()) {
continue;
}
if (it.value()->name() == "edit_file" && !settings.enableEditFileTool()) {
continue;
}
if (it.value()->name() == "build_project" && !settings.enableBuildProjectTool()) {
continue;
}
if (it.value()->name() == "execute_terminal_command"
&& !settings.enableTerminalCommandTool()) {
continue;
}
if (it.value()->name() == "todo_tool" && !settings.enableTodoTool()) {
continue;
}
const auto requiredPerms = it.value()->requiredPermissions();
if (filter != PluginLLMCore::RunToolsFilter::ALL) {
bool matchesFilter = false;
switch (filter) {
case PluginLLMCore::RunToolsFilter::OnlyRead:
if (requiredPerms == PluginLLMCore::ToolPermission::None
|| requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemRead)) {
matchesFilter = true;
}
break;
case PluginLLMCore::RunToolsFilter::OnlyWrite:
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemWrite)) {
matchesFilter = true;
}
break;
case PluginLLMCore::RunToolsFilter::OnlyNetworking:
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::NetworkAccess)) {
matchesFilter = true;
}
break;
case PluginLLMCore::RunToolsFilter::ALL:
matchesFilter = true;
break;
}
if (!matchesFilter) {
LOG_MESSAGE(QString("Tool '%1' skipped by tools filter")
.arg(it.value()->name()));
continue;
}
}
bool hasPermission = true;
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemRead)) {
if (!settings.allowFileSystemRead()) {
hasPermission = false;
}
}
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemWrite)) {
if (!settings.allowFileSystemWrite()) {
hasPermission = false;
}
}
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::NetworkAccess)) {
if (!settings.allowNetworkAccess()) {
hasPermission = false;
}
}
if (hasPermission) {
toolsArray.append(it.value()->getDefinition(format));
} else {
LOG_MESSAGE(
QString("Tool '%1' skipped due to missing permissions").arg(it.value()->name()));
}
}
return toolsArray;
}
QString ToolsFactory::getStringName(const QString &name) const
{
return m_tools.contains(name) ? m_tools.value(name)->stringName() : QString("Unknown tools");
}
} // namespace QodeAssist::Tools

View File

@@ -1,48 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <pluginllmcore/BaseTool.hpp>
namespace QodeAssist::Tools {
class ToolsFactory : public QObject
{
Q_OBJECT
public:
ToolsFactory(QObject *parent = nullptr);
~ToolsFactory() override = default;
QList<PluginLLMCore::BaseTool *> getAvailableTools() const;
PluginLLMCore::BaseTool *getToolByName(const QString &name) const;
QJsonArray getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format,
PluginLLMCore::RunToolsFilter filter = PluginLLMCore::RunToolsFilter::ALL) const;
QString getStringName(const QString &name) const;
private:
void registerTools();
void registerTool(PluginLLMCore::BaseTool *tool);
QHash<QString, PluginLLMCore::BaseTool *> m_tools;
};
} // namespace QodeAssist::Tools

View File

@@ -1,223 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "ToolsManager.hpp"
#include "TodoTool.hpp"
#include "logger/Logger.hpp"
#include <QTimer>
namespace {
constexpr int kToolExecutionDelayMs = 300;
}
namespace QodeAssist::Tools {
ToolsManager::ToolsManager(QObject *parent)
: QObject(parent)
, m_toolsFactory(new ToolsFactory(this))
, m_toolHandler(new ToolHandler(this))
{
connect(
m_toolHandler,
&ToolHandler::toolCompleted,
this,
[this](const QString &requestId, const QString &toolId, const QString &result) {
onToolFinished(requestId, toolId, result, true);
});
connect(
m_toolHandler,
&ToolHandler::toolFailed,
this,
[this](const QString &requestId, const QString &toolId, const QString &error) {
onToolFinished(requestId, toolId, error, false);
});
}
void ToolsManager::executeToolCall(
const QString &requestId,
const QString &toolId,
const QString &toolName,
const QJsonObject &input)
{
LOG_MESSAGE(QString("ToolsManager: Queueing tool %1 (ID: %2) for request %3")
.arg(toolName, toolId, requestId));
if (!m_toolQueues.contains(requestId)) {
m_toolQueues[requestId] = ToolQueue();
}
auto &queue = m_toolQueues[requestId];
for (const auto &tool : queue.queue) {
if (tool.id == toolId) {
LOG_MESSAGE(QString("Tool %1 already in queue for request %2").arg(toolId, requestId));
return;
}
}
if (queue.completed.contains(toolId)) {
LOG_MESSAGE(
QString("Tool %1 already completed for request %2").arg(toolId, requestId));
return;
}
QJsonObject modifiedInput = input;
modifiedInput["_request_id"] = requestId;
if (!m_currentSessionId.isEmpty()) {
modifiedInput["session_id"] = m_currentSessionId;
}
PendingTool pendingTool{toolId, toolName, modifiedInput, "", false};
queue.queue.append(pendingTool);
LOG_MESSAGE(QString("ToolsManager: Tool %1 added to queue (position %2)")
.arg(toolName)
.arg(queue.queue.size()));
if (!queue.isExecuting) {
executeNextTool(requestId);
}
}
void ToolsManager::executeNextTool(const QString &requestId)
{
if (!m_toolQueues.contains(requestId)) {
return;
}
auto &queue = m_toolQueues[requestId];
if (queue.queue.isEmpty()) {
LOG_MESSAGE(QString("ToolsManager: All tools complete for request %1, emitting results")
.arg(requestId));
QHash<QString, QString> results = getToolResults(requestId);
emit toolExecutionComplete(requestId, results);
queue.isExecuting = false;
return;
}
PendingTool tool = queue.queue.takeFirst();
queue.isExecuting = true;
LOG_MESSAGE(QString("ToolsManager: Executing tool %1 (ID: %2) for request %3 (%4 remaining)")
.arg(tool.name, tool.id, requestId)
.arg(queue.queue.size()));
auto toolInstance = m_toolsFactory->getToolByName(tool.name);
if (!toolInstance) {
LOG_MESSAGE(QString("ToolsManager: Tool not found: %1").arg(tool.name));
tool.result = QString("Error: Tool not found: %1").arg(tool.name);
tool.complete = true;
queue.completed[tool.id] = tool;
executeNextTool(requestId);
return;
}
queue.completed[tool.id] = tool;
m_toolHandler->executeToolAsync(requestId, tool.id, toolInstance, tool.input);
LOG_MESSAGE(QString("ToolsManager: Started async execution of %1").arg(tool.name));
}
QJsonArray ToolsManager::getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format, PluginLLMCore::RunToolsFilter filter) const
{
if (!m_toolsFactory) {
return QJsonArray();
}
return m_toolsFactory->getToolsDefinitions(format, filter);
}
void ToolsManager::cleanupRequest(const QString &requestId)
{
if (m_toolQueues.contains(requestId)) {
m_toolHandler->cleanupRequest(requestId);
m_toolQueues.remove(requestId);
}
}
void ToolsManager::onToolFinished(
const QString &requestId, const QString &toolId, const QString &result, bool success)
{
if (!m_toolQueues.contains(requestId)) {
return;
}
auto &queue = m_toolQueues[requestId];
if (!queue.completed.contains(toolId)) {
return;
}
PendingTool &tool = queue.completed[toolId];
tool.result = success ? result : QString("Error: %1").arg(result);
tool.complete = true;
LOG_MESSAGE(QString("ToolsManager: Tool %1 %2 for request %3")
.arg(toolId)
.arg(success ? QString("completed") : QString("failed"))
.arg(requestId));
if (kToolExecutionDelayMs > 0 && !queue.queue.isEmpty()) {
QTimer::singleShot(kToolExecutionDelayMs, this, [this, requestId]() {
executeNextTool(requestId);
});
} else {
executeNextTool(requestId);
}
}
ToolsFactory *ToolsManager::toolsFactory() const
{
return m_toolsFactory;
}
QHash<QString, QString> ToolsManager::getToolResults(const QString &requestId) const
{
QHash<QString, QString> results;
if (m_toolQueues.contains(requestId)) {
const auto &queue = m_toolQueues[requestId];
for (auto it = queue.completed.begin(); it != queue.completed.end(); ++it) {
if (it.value().complete) {
results[it.key()] = it.value().result;
}
}
}
return results;
}
void ToolsManager::clearTodoSession(const QString &sessionId)
{
auto *todoTool = qobject_cast<TodoTool *>(m_toolsFactory->getToolByName("todo_tool"));
if (todoTool) {
todoTool->clearSession(sessionId);
}
}
void ToolsManager::setCurrentSessionId(const QString &sessionId)
{
m_currentSessionId = sessionId;
}
} // namespace QodeAssist::Tools

View File

@@ -1,90 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QHash>
#include <QJsonArray>
#include <QJsonObject>
#include <QObject>
#include "ToolHandler.hpp"
#include "ToolsFactory.hpp"
#include <pluginllmcore/BaseTool.hpp>
#include <pluginllmcore/IToolsManager.hpp>
namespace QodeAssist::Tools {
struct PendingTool
{
QString id;
QString name;
QJsonObject input;
QString result;
bool complete = false;
};
struct ToolQueue
{
QList<PendingTool> queue;
QHash<QString, PendingTool> completed;
bool isExecuting = false;
};
class ToolsManager : public QObject, public PluginLLMCore::IToolsManager
{
Q_OBJECT
public:
explicit ToolsManager(QObject *parent = nullptr);
void executeToolCall(
const QString &requestId,
const QString &toolId,
const QString &toolName,
const QJsonObject &input) override;
QJsonArray getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format,
PluginLLMCore::RunToolsFilter filter = PluginLLMCore::RunToolsFilter::ALL) const override;
void cleanupRequest(const QString &requestId) override;
void setCurrentSessionId(const QString &sessionId) override;
void clearTodoSession(const QString &sessionId) override;
ToolsFactory *toolsFactory() const;
signals:
void toolExecutionComplete(const QString &requestId, const QHash<QString, QString> &toolResults);
private slots:
void onToolFinished(
const QString &requestId, const QString &toolId, const QString &result, bool success);
private:
ToolsFactory *m_toolsFactory;
ToolHandler *m_toolHandler;
QHash<QString, ToolQueue> m_toolQueues;
QString m_currentSessionId;
void executeNextTool(const QString &requestId);
QHash<QString, QString> getToolResults(const QString &requestId) const;
};
} // namespace QodeAssist::Tools

View File

@@ -19,7 +19,7 @@
#include "ToolsRegistration.hpp"
#include <LLMCore/ToolsManager.hpp>
#include <LLMQore/ToolsManager.hpp>
#include "BuildProjectTool.hpp"
#include "CreateNewFileTool.hpp"
@@ -33,7 +33,7 @@
namespace QodeAssist::Tools {
void registerQodeAssistTools(::LLMCore::ToolsManager *manager)
void registerQodeAssistTools(::LLMQore::ToolsManager *manager)
{
manager->addTool(new ListProjectFilesTool(manager));
manager->addTool(new GetIssuesListTool(manager));

View File

@@ -19,12 +19,12 @@
#pragma once
namespace LLMCore {
namespace LLMQore {
class ToolsManager;
}
namespace QodeAssist::Tools {
void registerQodeAssistTools(::LLMCore::ToolsManager *manager);
void registerQodeAssistTools(::LLMQore::ToolsManager *manager);
} // namespace QodeAssist::Tools

View File

@@ -640,8 +640,6 @@ void QuickRefactorDialog::onConfigurationChanged(int index)
settings.qrModel.setValue(config.model);
settings.qrTemplate.setValue(config.templateName);
settings.qrUrl.setValue(config.url);
settings.qrEndpointMode.setValue(
settings.qrEndpointMode.indexForDisplay(config.endpointMode));
settings.qrCustomEndpoint.setValue(config.customEndpoint);
settings.writeSettings();