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 /.cursor
/.vscode /.vscode
.qtc_clangd/compile_commands.json .qtc_clangd/compile_commands.json
CLAUDE.md
/.claude

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "sources/external/llmcore"] [submodule "sources/external/llmqore"]
path = sources/external/llmcore path = sources/external/llmqore
url = https://github.com/Palm1r/llmcore.git 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} -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(pluginllmcore)
add_subdirectory(settings) add_subdirectory(settings)
add_subdirectory(logger) add_subdirectory(logger)
@@ -62,7 +62,7 @@ add_qtc_plugin(QodeAssist
QtCreator::ExtensionSystem QtCreator::ExtensionSystem
QtCreator::Utils QtCreator::Utils
QtCreator::CPlusPlus QtCreator::CPlusPlus
LLMCore LLMQore
PluginLLMCore PluginLLMCore
QodeAssistChatViewplugin QodeAssistChatViewplugin
SOURCES SOURCES

View File

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

View File

@@ -19,7 +19,7 @@
#include "ChatCompressor.hpp" #include "ChatCompressor.hpp"
#include <LLMCore/BaseClient.hpp> #include <LLMQore/BaseClient.hpp>
#include "ChatModel.hpp" #include "ChatModel.hpp"
#include "GeneralSettings.hpp" #include "GeneralSettings.hpp"
#include "PromptTemplateManager.hpp" #include "PromptTemplateManager.hpp"
@@ -83,23 +83,16 @@ void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *ch
connectProviderSignals(); connectProviderSignals();
QUrl requestUrl; QJsonObject payload{
QJsonObject payload; {"model", Settings::generalSettings().caModel()}, {"stream", true}};
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;
}
buildRequestPayload(payload, promptTemplate); 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)); LOG_MESSAGE(QString("Starting compression request: %1").arg(m_currentRequestId));
} }
@@ -271,21 +264,21 @@ void ChatCompressor::connectProviderSignals()
m_connections.append(connect( m_connections.append(connect(
c, c,
&::LLMCore::BaseClient::chunkReceived, &::LLMQore::BaseClient::chunkReceived,
this, this,
&ChatCompressor::onPartialResponseReceived, &ChatCompressor::onPartialResponseReceived,
Qt::UniqueConnection)); Qt::UniqueConnection));
m_connections.append(connect( m_connections.append(connect(
c, c,
&::LLMCore::BaseClient::requestCompleted, &::LLMQore::BaseClient::requestCompleted,
this, this,
&ChatCompressor::onFullResponseReceived, &ChatCompressor::onFullResponseReceived,
Qt::UniqueConnection)); Qt::UniqueConnection));
m_connections.append(connect( m_connections.append(connect(
c, c,
&::LLMCore::BaseClient::requestFailed, &::LLMQore::BaseClient::requestFailed,
this, this,
&ChatCompressor::onRequestFailed, &ChatCompressor::onRequestFailed,
Qt::UniqueConnection)); Qt::UniqueConnection));

View File

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

View File

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

View File

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

View File

@@ -87,7 +87,9 @@ private:
PluginLLMCore::ContextData prepareContext( PluginLLMCore::ContextData prepareContext(
const QJsonObject &request, const Context::DocumentInfo &documentInfo); 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::CodeCompletionSettings &m_completeSettings;
const Settings::GeneralSettings &m_generalSettings; const Settings::GeneralSettings &m_generalSettings;

View File

@@ -19,7 +19,7 @@
#include "QuickRefactorHandler.hpp" #include "QuickRefactorHandler.hpp"
#include <LLMCore/BaseClient.hpp> #include <LLMQore/BaseClient.hpp>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QUuid> #include <QUuid>
@@ -30,7 +30,6 @@
#include <context/Utils.hpp> #include <context/Utils.hpp>
#include <pluginllmcore/PromptTemplateManager.hpp> #include <pluginllmcore/PromptTemplateManager.hpp>
#include <pluginllmcore/ProvidersManager.hpp> #include <pluginllmcore/ProvidersManager.hpp>
#include <pluginllmcore/RequestConfig.hpp>
#include <pluginllmcore/RulesLoader.hpp> #include <pluginllmcore/RulesLoader.hpp>
#include <logger/Logger.hpp> #include <logger/Logger.hpp>
#include <settings/ChatAssistantSettings.hpp> #include <settings/ChatAssistantSettings.hpp>
@@ -141,32 +140,15 @@ void QuickRefactorHandler::prepareAndSendRequest(
return; return;
} }
PluginLLMCore::LLMConfig config; QJsonObject payload{
config.requestType = PluginLLMCore::RequestType::QuickRefactoring; {"model", Settings::generalSettings().qrModel()}, {"stream", true}};
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}};
}
PluginLLMCore::ContextData context = prepareContext(editor, range, instructions); PluginLLMCore::ContextData context = prepareContext(editor, range, instructions);
bool enableTools = Settings::quickRefactorSettings().useTools(); bool enableTools = Settings::quickRefactorSettings().useTools();
bool enableThinking = Settings::quickRefactorSettings().useThinking(); bool enableThinking = Settings::quickRefactorSettings().useThinking();
provider->prepareRequest( provider->prepareRequest(
config.providerRequest, payload,
promptTemplate, promptTemplate,
context, context,
PluginLLMCore::RequestType::QuickRefactoring, PluginLLMCore::RequestType::QuickRefactoring,
@@ -177,19 +159,23 @@ void QuickRefactorHandler::prepareAndSendRequest(
connect( connect(
provider->client(), provider->client(),
&::LLMCore::BaseClient::requestCompleted, &::LLMQore::BaseClient::requestCompleted,
this, this,
&QuickRefactorHandler::handleFullResponse, &QuickRefactorHandler::handleFullResponse,
Qt::UniqueConnection); Qt::UniqueConnection);
connect( connect(
provider->client(), provider->client(),
&::LLMCore::BaseClient::requestFailed, &::LLMQore::BaseClient::requestFailed,
this, this,
&QuickRefactorHandler::handleRequestFailed, &QuickRefactorHandler::handleRequestFailed,
Qt::UniqueConnection); 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; m_lastRequestId = requestId;
QJsonObject request{{"id", 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) void QuickRefactorHandler::handleRequestFailed(const QString &requestId, const QString &error)
{ {
if (requestId == m_lastRequestId) { 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_activeRequests.remove(requestId);
m_isRefactoringInProgress = false; m_isRefactoringInProgress = false;
RefactorResult result; RefactorResult result;
result.success = false; result.success = false;
result.errorMessage = error; result.errorMessage = enriched;
result.editor = m_currentEditor; result.editor = m_currentEditor;
emit refactoringCompleted(result); 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 PromptProviderFim.hpp
PromptTemplate.hpp PromptTemplate.hpp
PromptTemplateManager.hpp PromptTemplateManager.cpp PromptTemplateManager.hpp PromptTemplateManager.cpp
RequestConfig.hpp
ProviderID.hpp ProviderID.hpp
HttpClient.hpp HttpClient.cpp HttpClient.hpp HttpClient.cpp
DataBuffers.hpp DataBuffers.hpp
SSEBuffer.hpp SSEBuffer.cpp SSEBuffer.hpp SSEBuffer.cpp
BaseTool.hpp BaseTool.cpp
ContentBlocks.hpp ContentBlocks.hpp
RulesLoader.hpp RulesLoader.cpp RulesLoader.hpp RulesLoader.cpp
ResponseCleaner.hpp ResponseCleaner.hpp
@@ -27,7 +25,7 @@ target_link_libraries(PluginLLMCore
QtCreator::Core QtCreator::Core
QtCreator::Utils QtCreator::Utils
QtCreator::ExtensionSystem QtCreator::ExtensionSystem
LLMCore LLMQore
PRIVATE PRIVATE
QodeAssistLogger 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 void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
virtual QString description() const = 0; virtual QString description() const = 0;
virtual bool isSupportProvider(ProviderID id) 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 } // namespace QodeAssist::PluginLLMCore

View File

@@ -19,8 +19,14 @@
#include "Provider.hpp" #include "Provider.hpp"
#include <LLMCore/BaseClient.hpp> #include <LLMQore/BaseClient.hpp>
#include <LLMCore/ToolsManager.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> #include <Logger.hpp>
@@ -30,19 +36,21 @@ Provider::Provider(QObject *parent)
: 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(); auto *c = client();
QUrl baseUrl(url); c->setUrl(url.toString());
baseUrl.setPath("");
c->setUrl(baseUrl.toString());
c->setApiKey(apiKey()); c->setApiKey(apiKey());
auto requestId = c->sendMessage(payload); auto requestId = c->sendMessage(payload, endpoint);
LOG_MESSAGE( 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; return requestId;
} }
@@ -53,9 +61,35 @@ void Provider::cancelRequest(const RequestID &requestId)
client()->cancelRequest(requestId); client()->cancelRequest(requestId);
} }
::LLMCore::ToolsManager *Provider::toolsManager() const ::LLMQore::ToolsManager *Provider::toolsManager() const
{ {
return client()->tools(); 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 } // namespace QodeAssist::PluginLLMCore

View File

@@ -26,11 +26,10 @@
#include <utils/environment.h> #include <utils/environment.h>
#include "ContextData.hpp" #include "ContextData.hpp"
#include "IToolsManager.hpp"
#include "PromptTemplate.hpp" #include "PromptTemplate.hpp"
#include "RequestType.hpp" #include "RequestType.hpp"
namespace LLMCore { namespace LLMQore {
class BaseClient; class BaseClient;
class ToolsManager; class ToolsManager;
} }
@@ -58,8 +57,6 @@ public:
virtual QString name() const = 0; virtual QString name() const = 0;
virtual QString url() const = 0; virtual QString url() const = 0;
virtual QString completionEndpoint() const = 0;
virtual QString chatEndpoint() const = 0;
virtual void prepareRequest( virtual void prepareRequest(
QJsonObject &request, QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt, PluginLLMCore::PromptTemplate *prompt,
@@ -72,12 +69,15 @@ public:
virtual ProviderID providerID() const = 0; virtual ProviderID providerID() const = 0;
virtual ProviderCapabilities capabilities() const { return {}; } virtual ProviderCapabilities capabilities() const { return {}; }
virtual ::LLMCore::BaseClient *client() const = 0; virtual ::LLMQore::BaseClient *client() const = 0;
virtual QString apiKey() 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); void cancelRequest(const RequestID &requestId);
::LLMCore::ToolsManager *toolsManager() const; ::LLMQore::ToolsManager *toolsManager() const;
QString enrichErrorMessage(const QString &error) const;
}; };
} // namespace QodeAssist::PluginLLMCore } // 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 <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <LLMCore/ToolsManager.hpp> #include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp" #include "settings/ChatAssistantSettings.hpp"
@@ -37,7 +37,7 @@ namespace QodeAssist::Providers {
ClaudeProvider::ClaudeProvider(QObject *parent) ClaudeProvider::ClaudeProvider(QObject *parent)
: PluginLLMCore::Provider(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()); Tools::registerQodeAssistTools(m_client->tools());
} }
@@ -57,16 +57,6 @@ QString ClaudeProvider::url() const
return "https://api.anthropic.com"; return "https://api.anthropic.com";
} }
QString ClaudeProvider::completionEndpoint() const
{
return "/v1/messages";
}
QString ClaudeProvider::chatEndpoint() const
{
return "/v1/messages";
}
void ClaudeProvider::prepareRequest( void ClaudeProvider::prepareRequest(
QJsonObject &request, QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt, PluginLLMCore::PromptTemplate *prompt,
@@ -151,7 +141,7 @@ PluginLLMCore::ProviderCapabilities ClaudeProvider::capabilities() const
| PluginLLMCore::ProviderCapability::ModelListing; | PluginLLMCore::ProviderCapability::ModelListing;
} }
::LLMCore::BaseClient *ClaudeProvider::client() const ::LLMQore::BaseClient *ClaudeProvider::client() const
{ {
return m_client; return m_client;
} }

View File

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

View File

@@ -19,7 +19,7 @@
#include "GoogleAIProvider.hpp" #include "GoogleAIProvider.hpp"
#include <LLMCore/ToolsManager.hpp> #include <LLMQore/ToolsManager.hpp>
#include <QJsonArray> #include <QJsonArray>
#include "tools/ToolsRegistration.hpp" #include "tools/ToolsRegistration.hpp"
@@ -38,7 +38,7 @@ namespace QodeAssist::Providers {
GoogleAIProvider::GoogleAIProvider(QObject *parent) GoogleAIProvider::GoogleAIProvider(QObject *parent)
: PluginLLMCore::Provider(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()); Tools::registerQodeAssistTools(m_client->tools());
} }
@@ -58,16 +58,6 @@ QString GoogleAIProvider::url() const
return "https://generativelanguage.googleapis.com/v1beta"; return "https://generativelanguage.googleapis.com/v1beta";
} }
QString GoogleAIProvider::completionEndpoint() const
{
return {};
}
QString GoogleAIProvider::chatEndpoint() const
{
return {};
}
void GoogleAIProvider::prepareRequest( void GoogleAIProvider::prepareRequest(
QJsonObject &request, QJsonObject &request,
PluginLLMCore::PromptTemplate *prompt, PluginLLMCore::PromptTemplate *prompt,
@@ -165,7 +155,25 @@ PluginLLMCore::ProviderCapabilities GoogleAIProvider::capabilities() const
| PluginLLMCore::ProviderCapability::ModelListing; | 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; return m_client;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@
#include "MistralAIProvider.hpp" #include "MistralAIProvider.hpp"
#include <LLMCore/ToolsManager.hpp> #include <LLMQore/ToolsManager.hpp>
#include "logger/Logger.hpp" #include "logger/Logger.hpp"
#include "settings/ChatAssistantSettings.hpp" #include "settings/ChatAssistantSettings.hpp"
#include "settings/CodeCompletionSettings.hpp" #include "settings/CodeCompletionSettings.hpp"
@@ -36,7 +36,7 @@ namespace QodeAssist::Providers {
MistralAIProvider::MistralAIProvider(QObject *parent) MistralAIProvider::MistralAIProvider(QObject *parent)
: PluginLLMCore::Provider(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()); Tools::registerQodeAssistTools(m_client->tools());
} }
@@ -56,16 +56,6 @@ QString MistralAIProvider::url() const
return "https://api.mistral.ai"; 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) QFuture<QList<QString>> MistralAIProvider::getInstalledModels(const QString &url)
{ {
m_client->setUrl(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; return m_client;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -62,7 +62,6 @@ public:
Utils::StringAspect ccUrl{this}; Utils::StringAspect ccUrl{this};
ButtonAspect ccSetUrl{this}; ButtonAspect ccSetUrl{this};
Utils::SelectionAspect ccEndpointMode{this};
Utils::StringAspect ccCustomEndpoint{this}; Utils::StringAspect ccCustomEndpoint{this};
Utils::StringAspect ccStatus{this}; Utils::StringAspect ccStatus{this};
@@ -85,7 +84,6 @@ public:
Utils::StringAspect ccPreset1Url{this}; Utils::StringAspect ccPreset1Url{this};
ButtonAspect ccPreset1SetUrl{this}; ButtonAspect ccPreset1SetUrl{this};
Utils::SelectionAspect ccPreset1EndpointMode{this};
Utils::StringAspect ccPreset1CustomEndpoint{this}; Utils::StringAspect ccPreset1CustomEndpoint{this};
Utils::StringAspect ccPreset1Model{this}; Utils::StringAspect ccPreset1Model{this};
@@ -110,7 +108,6 @@ public:
Utils::StringAspect caUrl{this}; Utils::StringAspect caUrl{this};
ButtonAspect caSetUrl{this}; ButtonAspect caSetUrl{this};
Utils::SelectionAspect caEndpointMode{this};
Utils::StringAspect caCustomEndpoint{this}; Utils::StringAspect caCustomEndpoint{this};
Utils::StringAspect caStatus{this}; Utils::StringAspect caStatus{this};
@@ -138,7 +135,6 @@ public:
Utils::StringAspect qrUrl{this}; Utils::StringAspect qrUrl{this};
ButtonAspect qrSetUrl{this}; ButtonAspect qrSetUrl{this};
Utils::SelectionAspect qrEndpointMode{this};
Utils::StringAspect qrCustomEndpoint{this}; Utils::StringAspect qrCustomEndpoint{this};
Utils::StringAspect qrStatus{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_TEMPLATE[] = "QodeAssist.ccTemplate";
const char CC_URL[] = "QodeAssist.ccUrl"; const char CC_URL[] = "QodeAssist.ccUrl";
const char CC_URL_HISTORY[] = "QodeAssist.ccUrlHistory"; 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[] = "QodeAssist.ccCustomEndpoint";
const char CC_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.ccCustomEndpointHistory"; 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_TEMPLATE[] = "QodeAssist.caTemplate";
const char CA_URL[] = "QodeAssist.caUrl"; const char CA_URL[] = "QodeAssist.caUrl";
const char CA_URL_HISTORY[] = "QodeAssist.caUrlHistory"; 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[] = "QodeAssist.caCustomEndpoint";
const char CA_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caCustomEndpointHistory"; 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_TEMPLATE[] = "QodeAssist.qrTemplate";
const char QR_URL[] = "QodeAssist.qrUrl"; const char QR_URL[] = "QodeAssist.qrUrl";
const char QR_URL_HISTORY[] = "QodeAssist.qrUrlHistory"; 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[] = "QodeAssist.qrCustomEndpoint";
const char QR_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.qrCustomEndpointHistory"; 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_TEMPLATE[] = "QodeAssist.ccPreset1Template";
const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url"; const char CC_PRESET1_URL[] = "QodeAssist.ccPreset1Url";
const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory"; const char CC_PRESET1_URL_HISTORY[] = "QodeAssist.ccPreset1UrlHistory";
const char CC_PRESET1_ENDPOINT_MODE[] = "QodeAssist.caPreset1EndpointMode"; const char CC_PRESET1_CUSTOM_ENDPOINT[] = "QodeAssist.ccPreset1CustomEndpoint";
const char CC_PRESET1_CUSTOM_ENDPOINT[] = "QodeAssist.caPreset1CustomEndpointHistory"; const char CC_PRESET1_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.ccPreset1CustomEndpointHistory";
const char CC_PRESET1_CUSTOM_ENDPOINT_HISTORY[] = "QodeAssist.caPreset1CustomEndpointHistory";
// settings // settings
const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist"; 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 MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold";
const char CC_MULTILINE_COMPLETION[] = "QodeAssist.ccMultilineCompletion"; const char CC_MULTILINE_COMPLETION[] = "QodeAssist.ccMultilineCompletion";
const char CC_MODEL_OUTPUT_HANDLER[] = "QodeAssist.ccModelOutputHandler"; 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_AUTO_APPLY_FILE_EDITS[] = "QodeAssist.caAutoApplyFileEdits";
const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold"; const char CA_TOKENS_THRESHOLD[] = "QodeAssist.caTokensThreshold";
const char CA_LINK_OPEN_FILES[] = "QodeAssist.caLinkOpenFiles"; 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[] const char QODE_ASSIST_QUICK_REFACTOR_SETTINGS_PAGE_ID[]
= "QodeAssist.4QuickRefactorSettingsPageId"; = "QodeAssist.4QuickRefactorSettingsPageId";
const char QODE_ASSIST_TOOLS_SETTINGS_PAGE_ID[] = "QodeAssist.5ToolsSettingsPageId"; 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_CATEGORY[] = "QodeAssist.Category";
const char QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "QodeAssist"; 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( inline const char *ENABLE_CHAT = QT_TRANSLATE_NOOP(
"QtC::QodeAssist", "QtC::QodeAssist",
"Enable Chat(If you have performance issues try disabling this, need restart QtC)"); "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 *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant"); 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: public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; } PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "CodeLlama FIM"; } QString name() const override { return "CodeLlama FIM"; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override QStringList stopWords() const override
{ {
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>"; return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";

View File

@@ -28,6 +28,7 @@ class CodeLlamaQMLFim : public PluginLLMCore::PromptTemplate
public: public:
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; } PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
QString name() const override { return "CodeLlama QML FIM"; } QString name() const override { return "CodeLlama QML FIM"; }
QString endpoint() const override { return QStringLiteral("/api/generate"); }
QStringList stopWords() const override QStringList stopWords() const override
{ {
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end" 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 { namespace QodeAssist::Templates {
class DeepSeekCoderFim : public LLMCore::PromptTemplate class DeepSeekCoderFim : public LLMQore::PromptTemplate
{ {
public: 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 name() const override { return "DeepSeekCoder FIM"; }
QString promptTemplate() const override QString promptTemplate() const override
{ {
return "<fim▁begin>%1<fim▁hole>%2<fim▁end>"; return "<fim▁begin>%1<fim▁hole>%2<fim▁end>";
} }
QStringList stopWords() const override { return QStringList(); } 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); QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
request["prompt"] = formattedPrompt; request["prompt"] = formattedPrompt;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ target_link_libraries(QodeAssistTest PRIVATE
QtCreator::LanguageClient QtCreator::LanguageClient
Context Context
PluginLLMCore PluginLLMCore
LLMCore LLMQore
) )
target_compile_definitions(QodeAssistTest PRIVATE CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") 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 } // namespace QodeAssist::PluginLLMCore
using namespace QodeAssist::Context; using namespace QodeAssist::Context;
using namespace QodeAssist::LLMCore; using namespace QodeAssist::PluginLLMCore;
using namespace QodeAssist::Settings; using namespace QodeAssist::Settings;
class DocumentContextReaderTest : public QObject, public testing::Test 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; 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{" out << "Message{"
<< "role=" << value.role << "content=" << value.content << "}"; << "role=" << value.role << "content=" << value.content << "}";
return out; 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{" out << "ContextData{"
<< "\n systemPrompt=" << value.systemPrompt << "\n prefix=" << value.prefix << "\n systemPrompt=" << value.systemPrompt << "\n prefix=" << value.prefix
@@ -77,4 +77,4 @@ inline std::ostream &operator<<(std::ostream &out, const PluginLLMCore::ContextD
return out; return out;
} }
} // namespace QodeAssist::LLMCore } // namespace QodeAssist::PluginLLMCore

View File

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

View File

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

View File

@@ -18,7 +18,8 @@
*/ */
#include "CreateNewFileTool.hpp" #include "CreateNewFileTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <context/ProjectUtils.hpp> #include <context/ProjectUtils.hpp>
#include <logger/Logger.hpp> #include <logger/Logger.hpp>
@@ -73,13 +74,13 @@ QJsonObject CreateNewFileTool::parametersSchema() const
return definition; 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(); QString filePath = input["filepath"].toString();
if (filePath.isEmpty()) { if (filePath.isEmpty()) {
throw ToolInvalidArgument("Error: 'filepath' parameter is required"); throw LLMQore::ToolInvalidArgument("Error: 'filepath' parameter is required");
} }
QFileInfo fileInfo(filePath); QFileInfo fileInfo(filePath);
@@ -90,7 +91,7 @@ QFuture<QString> CreateNewFileTool::executeAsync(const QJsonObject &input)
if (!isInProject) { if (!isInProject) {
const auto &settings = Settings::toolsSettings(); const auto &settings = Settings::toolsSettings();
if (!settings.allowAccessOutsideProject()) { if (!settings.allowAccessOutsideProject()) {
throw ToolRuntimeError( throw LLMQore::ToolRuntimeError(
QString("Error: File path '%1' is not within the current project. " 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.") "Enable 'Allow file access outside project' in settings to create files outside project scope.")
.arg(absolutePath)); .arg(absolutePath));
@@ -99,14 +100,14 @@ QFuture<QString> CreateNewFileTool::executeAsync(const QJsonObject &input)
} }
if (fileInfo.exists()) { if (fileInfo.exists()) {
throw ToolRuntimeError( throw LLMQore::ToolRuntimeError(
QString("Error: File already exists at path '%1'").arg(filePath)); QString("Error: File already exists at path '%1'").arg(filePath));
} }
QDir dir = fileInfo.absoluteDir(); QDir dir = fileInfo.absoluteDir();
if (!dir.exists()) { if (!dir.exists()) {
if (!dir.mkpath(".")) { if (!dir.mkpath(".")) {
throw ToolRuntimeError( throw LLMQore::ToolRuntimeError(
QString("Error: Could not create directory: '%1'").arg(dir.absolutePath())); QString("Error: Could not create directory: '%1'").arg(dir.absolutePath()));
} }
LOG_MESSAGE(QString("Created directory path: %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); QFile file(absolutePath);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
throw ToolRuntimeError( throw LLMQore::ToolRuntimeError(
QString("Error: Could not create file '%1': %2").arg(absolutePath, file.errorString())); 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)); 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 #pragma once
#include <LLMCore/BaseTool.hpp> #include <LLMQore/BaseTool.hpp>
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
class CreateNewFileTool : public ::LLMCore::BaseTool class CreateNewFileTool : public ::LLMQore::BaseTool
{ {
Q_OBJECT Q_OBJECT
public: public:
@@ -34,7 +34,7 @@ public:
QString description() const override; QString description() const override;
QJsonObject parametersSchema() 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 } // namespace QodeAssist::Tools

View File

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

View File

@@ -19,11 +19,11 @@
#pragma once #pragma once
#include <LLMCore/BaseTool.hpp> #include <LLMQore/BaseTool.hpp>
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
class EditFileTool : public ::LLMCore::BaseTool class EditFileTool : public ::LLMQore::BaseTool
{ {
Q_OBJECT Q_OBJECT
public: public:
@@ -34,7 +34,7 @@ public:
QString description() const override; QString description() const override;
QJsonObject parametersSchema() 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 } // namespace QodeAssist::Tools

View File

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

View File

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

View File

@@ -18,7 +18,8 @@
*/ */
#include "FindAndReadFileTool.hpp" #include "FindAndReadFileTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <logger/Logger.hpp> #include <logger/Logger.hpp>
#include <QJsonArray> #include <QJsonArray>
@@ -71,12 +72,12 @@ QJsonObject FindAndReadFileTool::parametersSchema() const
return definition; 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(); QString query = input["query"].toString().trimmed();
if (query.isEmpty()) { if (query.isEmpty()) {
throw ToolInvalidArgument("Query parameter is required"); throw LLMQore::ToolInvalidArgument("Query parameter is required");
} }
QString filePattern = input["file_pattern"].toString(); QString filePattern = input["file_pattern"].toString();
@@ -90,7 +91,7 @@ QFuture<QString> FindAndReadFileTool::executeAsync(const QJsonObject &input)
query, filePattern, 10, m_ignoreManager); query, filePattern, 10, m_ignoreManager);
if (bestMatch.absolutePath.isEmpty()) { 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) { 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 "FileSearchUtils.hpp"
#include <context/IgnoreManager.hpp> #include <context/IgnoreManager.hpp>
#include <LLMCore/BaseTool.hpp> #include <LLMQore/BaseTool.hpp>
#include <QFuture> #include <QFuture>
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
class FindAndReadFileTool : public ::LLMCore::BaseTool class FindAndReadFileTool : public ::LLMQore::BaseTool
{ {
Q_OBJECT Q_OBJECT
@@ -40,7 +40,7 @@ public:
QString displayName() const override; QString displayName() const override;
QString description() const override; QString description() const override;
QJsonObject parametersSchema() const override; QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input) override; QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
private: private:
QString formatResult(const FileSearchUtils::FileMatch &match, bool readContent) const; QString formatResult(const FileSearchUtils::FileMatch &match, bool readContent) const;

View File

@@ -155,16 +155,16 @@ QJsonObject GetIssuesListTool::parametersSchema() const
return definition; 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"); QString severityFilter = input.value("severity").toString("all");
const auto tasks = IssuesTracker::instance().getTasks(); const auto tasks = IssuesTracker::instance().getTasks();
if (tasks.isEmpty()) { 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; QStringList results;
@@ -235,7 +235,7 @@ QFuture<QString> GetIssuesListTool::executeAsync(const QJsonObject &input)
.arg(processedCount); .arg(processedCount);
results.prepend(summary); results.prepend(summary);
return results.join("\n\n"); return LLMQore::ToolResult::text(results.join("\n\n"));
}); });
} }

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,8 @@
*/ */
#include "ProjectSearchTool.hpp" #include "ProjectSearchTool.hpp"
#include "ToolExceptions.hpp"
#include <LLMQore/ToolExceptions.hpp>
#include <cplusplus/Overview.h> #include <cplusplus/Overview.h>
#include <cplusplus/Scope.h> #include <cplusplus/Scope.h>
@@ -97,17 +98,17 @@ QJsonObject ProjectSearchTool::parametersSchema() const
return definition; 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(); QString query = input["query"].toString().trimmed();
if (query.isEmpty()) { if (query.isEmpty()) {
throw ToolInvalidArgument("Query parameter is required"); throw LLMQore::ToolInvalidArgument("Query parameter is required");
} }
QString searchTypeStr = input["search_type"].toString(); QString searchTypeStr = input["search_type"].toString();
if (searchTypeStr != "text" && searchTypeStr != "symbol") { 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; SearchType searchType = (searchTypeStr == "symbol") ? SearchType::Symbol : SearchType::Text;
@@ -129,10 +130,10 @@ QFuture<QString> ProjectSearchTool::executeAsync(const QJsonObject &input)
} }
if (results.isEmpty()) { 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 #pragma once
#include <context/IgnoreManager.hpp> #include <context/IgnoreManager.hpp>
#include <LLMCore/BaseTool.hpp> #include <LLMQore/BaseTool.hpp>
#include <QFuture> #include <QFuture>
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
class ProjectSearchTool : public ::LLMCore::BaseTool class ProjectSearchTool : public ::LLMQore::BaseTool
{ {
Q_OBJECT Q_OBJECT
@@ -38,7 +38,7 @@ public:
QString displayName() const override; QString displayName() const override;
QString description() const override; QString description() const override;
QJsonObject parametersSchema() const override; QJsonObject parametersSchema() const override;
QFuture<QString> executeAsync(const QJsonObject &input) override; QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
private: private:
enum class SearchType { Text, Symbol }; enum class SearchType { Text, Symbol };

View File

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

View File

@@ -19,7 +19,7 @@
#pragma once #pragma once
#include <LLMCore/BaseTool.hpp> #include <LLMQore/BaseTool.hpp>
#include <QHash> #include <QHash>
#include <QMutex> #include <QMutex>
@@ -34,7 +34,7 @@ struct TodoItem
bool completed; bool completed;
}; };
class TodoTool : public ::LLMCore::BaseTool class TodoTool : public ::LLMQore::BaseTool
{ {
Q_OBJECT Q_OBJECT
@@ -46,7 +46,7 @@ public:
QString description() const override; QString description() const override;
QJsonObject parametersSchema() 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 setCurrentSessionId(const QString &sessionId);
void clearSession(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 "ToolsRegistration.hpp"
#include <LLMCore/ToolsManager.hpp> #include <LLMQore/ToolsManager.hpp>
#include "BuildProjectTool.hpp" #include "BuildProjectTool.hpp"
#include "CreateNewFileTool.hpp" #include "CreateNewFileTool.hpp"
@@ -33,7 +33,7 @@
namespace QodeAssist::Tools { namespace QodeAssist::Tools {
void registerQodeAssistTools(::LLMCore::ToolsManager *manager) void registerQodeAssistTools(::LLMQore::ToolsManager *manager)
{ {
manager->addTool(new ListProjectFilesTool(manager)); manager->addTool(new ListProjectFilesTool(manager));
manager->addTool(new GetIssuesListTool(manager)); manager->addTool(new GetIssuesListTool(manager));

View File

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

View File

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