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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,142 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ToolHandler.hpp"
#include "ToolExceptions.hpp"
#include <QJsonDocument>
#include <QTimer>
#include <QtConcurrent>
#include "logger/Logger.hpp"
namespace QodeAssist::Tools {
ToolHandler::ToolHandler(QObject *parent)
: QObject(parent)
{}
QFuture<QString> ToolHandler::executeToolAsync(
const QString &requestId,
const QString &toolId,
PluginLLMCore::BaseTool *tool,
const QJsonObject &input)
{
if (!tool) {
return QtConcurrent::run([]() -> QString { throw std::runtime_error("Tool is null"); });
}
auto execution = std::make_unique<ToolExecution>();
execution->requestId = requestId;
execution->toolId = toolId;
execution->toolName = tool->name();
execution->watcher = new QFutureWatcher<QString>(this);
connect(execution->watcher, &QFutureWatcher<QString>::finished, this, [this, toolId]() {
onToolExecutionFinished(toolId);
});
LOG_MESSAGE(QString("Starting tool execution: %1 (ID: %2)").arg(tool->name(), toolId));
auto future = tool->executeAsync(input);
execution->watcher->setFuture(future);
m_activeExecutions.insert(toolId, execution.release());
return future;
}
void ToolHandler::cleanupRequest(const QString &requestId)
{
auto it = m_activeExecutions.begin();
while (it != m_activeExecutions.end()) {
if (it.value()->requestId == requestId) {
auto execution = it.value();
LOG_MESSAGE(
QString("Canceling tool %1 for request %2").arg(execution->toolName, requestId));
if (execution->watcher) {
execution->watcher->cancel();
execution->watcher->deleteLater();
}
delete execution;
it = m_activeExecutions.erase(it);
} else {
++it;
}
}
}
void ToolHandler::onToolExecutionFinished(const QString &toolId)
{
if (!m_activeExecutions.contains(toolId)) {
return;
}
auto execution = m_activeExecutions.take(toolId);
try {
QString result = execution->watcher->result();
LOG_MESSAGE(QString("Tool %1 completed").arg(execution->toolName));
emit toolCompleted(execution->requestId, execution->toolId, result);
} catch (const ToolException &e) {
QString error = e.message();
if (error.isEmpty()) {
error = "Tool execution failed with empty error message";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const QException &e) {
QString error = QString::fromUtf8(e.what());
if (error.isEmpty()) {
error = "Tool execution failed (QException with empty message)";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const std::runtime_error &e) {
QString error = QString::fromStdString(e.what());
if (error.isEmpty()) {
error = "Unknown runtime error occurred";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const std::invalid_argument &e) {
QString error = QString::fromStdString(e.what());
if (error.isEmpty()) {
error = "Invalid argument provided";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (const std::exception &e) {
QString error = QString::fromStdString(e.what());
if (error.isEmpty()) {
error = "Unknown exception occurred";
}
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
} catch (...) {
QString error = "Unknown error occurred during tool execution";
LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error));
emit toolFailed(execution->requestId, execution->toolId, error);
}
execution->watcher->deleteLater();
delete execution;
}
} // namespace QodeAssist::Tools

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFutureWatcher>
#include <QHash>
#include <QJsonObject>
#include <QObject>
#include <QString>
#include <pluginllmcore/BaseTool.hpp>
namespace QodeAssist::Tools {
class ToolHandler : public QObject
{
Q_OBJECT
public:
explicit ToolHandler(QObject *parent = nullptr);
QFuture<QString> executeToolAsync(
const QString &requestId,
const QString &toolId,
PluginLLMCore::BaseTool *tool,
const QJsonObject &input);
void cleanupRequest(const QString &requestId);
signals:
void toolCompleted(const QString &requestId, const QString &toolId, const QString &result);
void toolFailed(const QString &requestId, const QString &toolId, const QString &error);
private:
struct ToolExecution
{
QString requestId;
QString toolId;
QString toolName;
QFutureWatcher<QString> *watcher;
};
QHash<QString, ToolExecution *> m_activeExecutions; // toolId -> execution
void onToolExecutionFinished(const QString &toolId);
};
} // namespace QodeAssist::Tools

View File

@@ -1,187 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ToolsFactory.hpp"
#include "logger/Logger.hpp"
#include <settings/GeneralSettings.hpp>
#include <settings/ToolsSettings.hpp>
#include <QJsonArray>
#include <QJsonObject>
#include "BuildProjectTool.hpp"
#include "CreateNewFileTool.hpp"
#include "EditFileTool.hpp"
#include "ExecuteTerminalCommandTool.hpp"
#include "FindAndReadFileTool.hpp"
#include "GetIssuesListTool.hpp"
#include "ListProjectFilesTool.hpp"
#include "ProjectSearchTool.hpp"
#include "TodoTool.hpp"
namespace QodeAssist::Tools {
ToolsFactory::ToolsFactory(QObject *parent)
: QObject(parent)
{
registerTools();
}
void ToolsFactory::registerTools()
{
registerTool(new ListProjectFilesTool(this));
registerTool(new GetIssuesListTool(this));
registerTool(new CreateNewFileTool(this));
registerTool(new EditFileTool(this));
registerTool(new BuildProjectTool(this));
registerTool(new ExecuteTerminalCommandTool(this));
registerTool(new ProjectSearchTool(this));
registerTool(new FindAndReadFileTool(this));
registerTool(new TodoTool(this));
LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size()));
}
void ToolsFactory::registerTool(PluginLLMCore::BaseTool *tool)
{
if (!tool) {
LOG_MESSAGE("Warning: Attempted to register null tool");
return;
}
const QString toolName = tool->name();
if (m_tools.contains(toolName)) {
LOG_MESSAGE(QString("Warning: Tool '%1' already registered, replacing").arg(toolName));
}
m_tools.insert(toolName, tool);
}
QList<PluginLLMCore::BaseTool *> ToolsFactory::getAvailableTools() const
{
return m_tools.values();
}
PluginLLMCore::BaseTool *ToolsFactory::getToolByName(const QString &name) const
{
return m_tools.value(name, nullptr);
}
QJsonArray ToolsFactory::getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format, PluginLLMCore::RunToolsFilter filter) const
{
QJsonArray toolsArray;
const auto &settings = Settings::toolsSettings();
for (auto it = m_tools.constBegin(); it != m_tools.constEnd(); ++it) {
if (!it.value()) {
continue;
}
if (it.value()->name() == "edit_file" && !settings.enableEditFileTool()) {
continue;
}
if (it.value()->name() == "build_project" && !settings.enableBuildProjectTool()) {
continue;
}
if (it.value()->name() == "execute_terminal_command"
&& !settings.enableTerminalCommandTool()) {
continue;
}
if (it.value()->name() == "todo_tool" && !settings.enableTodoTool()) {
continue;
}
const auto requiredPerms = it.value()->requiredPermissions();
if (filter != PluginLLMCore::RunToolsFilter::ALL) {
bool matchesFilter = false;
switch (filter) {
case PluginLLMCore::RunToolsFilter::OnlyRead:
if (requiredPerms == PluginLLMCore::ToolPermission::None
|| requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemRead)) {
matchesFilter = true;
}
break;
case PluginLLMCore::RunToolsFilter::OnlyWrite:
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemWrite)) {
matchesFilter = true;
}
break;
case PluginLLMCore::RunToolsFilter::OnlyNetworking:
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::NetworkAccess)) {
matchesFilter = true;
}
break;
case PluginLLMCore::RunToolsFilter::ALL:
matchesFilter = true;
break;
}
if (!matchesFilter) {
LOG_MESSAGE(QString("Tool '%1' skipped by tools filter")
.arg(it.value()->name()));
continue;
}
}
bool hasPermission = true;
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemRead)) {
if (!settings.allowFileSystemRead()) {
hasPermission = false;
}
}
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::FileSystemWrite)) {
if (!settings.allowFileSystemWrite()) {
hasPermission = false;
}
}
if (requiredPerms.testFlag(PluginLLMCore::ToolPermission::NetworkAccess)) {
if (!settings.allowNetworkAccess()) {
hasPermission = false;
}
}
if (hasPermission) {
toolsArray.append(it.value()->getDefinition(format));
} else {
LOG_MESSAGE(
QString("Tool '%1' skipped due to missing permissions").arg(it.value()->name()));
}
}
return toolsArray;
}
QString ToolsFactory::getStringName(const QString &name) const
{
return m_tools.contains(name) ? m_tools.value(name)->stringName() : QString("Unknown tools");
}
} // namespace QodeAssist::Tools

View File

@@ -1,48 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <pluginllmcore/BaseTool.hpp>
namespace QodeAssist::Tools {
class ToolsFactory : public QObject
{
Q_OBJECT
public:
ToolsFactory(QObject *parent = nullptr);
~ToolsFactory() override = default;
QList<PluginLLMCore::BaseTool *> getAvailableTools() const;
PluginLLMCore::BaseTool *getToolByName(const QString &name) const;
QJsonArray getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format,
PluginLLMCore::RunToolsFilter filter = PluginLLMCore::RunToolsFilter::ALL) const;
QString getStringName(const QString &name) const;
private:
void registerTools();
void registerTool(PluginLLMCore::BaseTool *tool);
QHash<QString, PluginLLMCore::BaseTool *> m_tools;
};
} // namespace QodeAssist::Tools

View File

@@ -1,223 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ToolsManager.hpp"
#include "TodoTool.hpp"
#include "logger/Logger.hpp"
#include <QTimer>
namespace {
constexpr int kToolExecutionDelayMs = 300;
}
namespace QodeAssist::Tools {
ToolsManager::ToolsManager(QObject *parent)
: QObject(parent)
, m_toolsFactory(new ToolsFactory(this))
, m_toolHandler(new ToolHandler(this))
{
connect(
m_toolHandler,
&ToolHandler::toolCompleted,
this,
[this](const QString &requestId, const QString &toolId, const QString &result) {
onToolFinished(requestId, toolId, result, true);
});
connect(
m_toolHandler,
&ToolHandler::toolFailed,
this,
[this](const QString &requestId, const QString &toolId, const QString &error) {
onToolFinished(requestId, toolId, error, false);
});
}
void ToolsManager::executeToolCall(
const QString &requestId,
const QString &toolId,
const QString &toolName,
const QJsonObject &input)
{
LOG_MESSAGE(QString("ToolsManager: Queueing tool %1 (ID: %2) for request %3")
.arg(toolName, toolId, requestId));
if (!m_toolQueues.contains(requestId)) {
m_toolQueues[requestId] = ToolQueue();
}
auto &queue = m_toolQueues[requestId];
for (const auto &tool : queue.queue) {
if (tool.id == toolId) {
LOG_MESSAGE(QString("Tool %1 already in queue for request %2").arg(toolId, requestId));
return;
}
}
if (queue.completed.contains(toolId)) {
LOG_MESSAGE(
QString("Tool %1 already completed for request %2").arg(toolId, requestId));
return;
}
QJsonObject modifiedInput = input;
modifiedInput["_request_id"] = requestId;
if (!m_currentSessionId.isEmpty()) {
modifiedInput["session_id"] = m_currentSessionId;
}
PendingTool pendingTool{toolId, toolName, modifiedInput, "", false};
queue.queue.append(pendingTool);
LOG_MESSAGE(QString("ToolsManager: Tool %1 added to queue (position %2)")
.arg(toolName)
.arg(queue.queue.size()));
if (!queue.isExecuting) {
executeNextTool(requestId);
}
}
void ToolsManager::executeNextTool(const QString &requestId)
{
if (!m_toolQueues.contains(requestId)) {
return;
}
auto &queue = m_toolQueues[requestId];
if (queue.queue.isEmpty()) {
LOG_MESSAGE(QString("ToolsManager: All tools complete for request %1, emitting results")
.arg(requestId));
QHash<QString, QString> results = getToolResults(requestId);
emit toolExecutionComplete(requestId, results);
queue.isExecuting = false;
return;
}
PendingTool tool = queue.queue.takeFirst();
queue.isExecuting = true;
LOG_MESSAGE(QString("ToolsManager: Executing tool %1 (ID: %2) for request %3 (%4 remaining)")
.arg(tool.name, tool.id, requestId)
.arg(queue.queue.size()));
auto toolInstance = m_toolsFactory->getToolByName(tool.name);
if (!toolInstance) {
LOG_MESSAGE(QString("ToolsManager: Tool not found: %1").arg(tool.name));
tool.result = QString("Error: Tool not found: %1").arg(tool.name);
tool.complete = true;
queue.completed[tool.id] = tool;
executeNextTool(requestId);
return;
}
queue.completed[tool.id] = tool;
m_toolHandler->executeToolAsync(requestId, tool.id, toolInstance, tool.input);
LOG_MESSAGE(QString("ToolsManager: Started async execution of %1").arg(tool.name));
}
QJsonArray ToolsManager::getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format, PluginLLMCore::RunToolsFilter filter) const
{
if (!m_toolsFactory) {
return QJsonArray();
}
return m_toolsFactory->getToolsDefinitions(format, filter);
}
void ToolsManager::cleanupRequest(const QString &requestId)
{
if (m_toolQueues.contains(requestId)) {
m_toolHandler->cleanupRequest(requestId);
m_toolQueues.remove(requestId);
}
}
void ToolsManager::onToolFinished(
const QString &requestId, const QString &toolId, const QString &result, bool success)
{
if (!m_toolQueues.contains(requestId)) {
return;
}
auto &queue = m_toolQueues[requestId];
if (!queue.completed.contains(toolId)) {
return;
}
PendingTool &tool = queue.completed[toolId];
tool.result = success ? result : QString("Error: %1").arg(result);
tool.complete = true;
LOG_MESSAGE(QString("ToolsManager: Tool %1 %2 for request %3")
.arg(toolId)
.arg(success ? QString("completed") : QString("failed"))
.arg(requestId));
if (kToolExecutionDelayMs > 0 && !queue.queue.isEmpty()) {
QTimer::singleShot(kToolExecutionDelayMs, this, [this, requestId]() {
executeNextTool(requestId);
});
} else {
executeNextTool(requestId);
}
}
ToolsFactory *ToolsManager::toolsFactory() const
{
return m_toolsFactory;
}
QHash<QString, QString> ToolsManager::getToolResults(const QString &requestId) const
{
QHash<QString, QString> results;
if (m_toolQueues.contains(requestId)) {
const auto &queue = m_toolQueues[requestId];
for (auto it = queue.completed.begin(); it != queue.completed.end(); ++it) {
if (it.value().complete) {
results[it.key()] = it.value().result;
}
}
}
return results;
}
void ToolsManager::clearTodoSession(const QString &sessionId)
{
auto *todoTool = qobject_cast<TodoTool *>(m_toolsFactory->getToolByName("todo_tool"));
if (todoTool) {
todoTool->clearSession(sessionId);
}
}
void ToolsManager::setCurrentSessionId(const QString &sessionId)
{
m_currentSessionId = sessionId;
}
} // namespace QodeAssist::Tools

View File

@@ -1,90 +0,0 @@
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QHash>
#include <QJsonArray>
#include <QJsonObject>
#include <QObject>
#include "ToolHandler.hpp"
#include "ToolsFactory.hpp"
#include <pluginllmcore/BaseTool.hpp>
#include <pluginllmcore/IToolsManager.hpp>
namespace QodeAssist::Tools {
struct PendingTool
{
QString id;
QString name;
QJsonObject input;
QString result;
bool complete = false;
};
struct ToolQueue
{
QList<PendingTool> queue;
QHash<QString, PendingTool> completed;
bool isExecuting = false;
};
class ToolsManager : public QObject, public PluginLLMCore::IToolsManager
{
Q_OBJECT
public:
explicit ToolsManager(QObject *parent = nullptr);
void executeToolCall(
const QString &requestId,
const QString &toolId,
const QString &toolName,
const QJsonObject &input) override;
QJsonArray getToolsDefinitions(
PluginLLMCore::ToolSchemaFormat format,
PluginLLMCore::RunToolsFilter filter = PluginLLMCore::RunToolsFilter::ALL) const override;
void cleanupRequest(const QString &requestId) override;
void setCurrentSessionId(const QString &sessionId) override;
void clearTodoSession(const QString &sessionId) override;
ToolsFactory *toolsFactory() const;
signals:
void toolExecutionComplete(const QString &requestId, const QHash<QString, QString> &toolResults);
private slots:
void onToolFinished(
const QString &requestId, const QString &toolId, const QString &result, bool success);
private:
ToolsFactory *m_toolsFactory;
ToolHandler *m_toolHandler;
QHash<QString, ToolQueue> m_toolQueues;
QString m_currentSessionId;
void executeNextTool(const QString &requestId);
QHash<QString, QString> getToolResults(const QString &requestId) const;
};
} // namespace QodeAssist::Tools

View File

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

View File

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