From d0f8c1098f42d2c65776d8588dfdfb314c8e554a Mon Sep 17 00:00:00 2001
From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com>
Date: Mon, 22 Sep 2025 12:36:13 +0200
Subject: [PATCH] feat: Add tools, fabric and executable handler (#230)
---
CMakeLists.txt | 4 +
llmcore/BaseTool.cpp | 88 ++++++++++++
llmcore/{ITool.hpp => BaseTool.hpp} | 26 ++--
llmcore/CMakeLists.txt | 4 +-
tools/ReadProjectFileByNameTool.cpp | 135 ++++++++++++++++++
tools/ReadProjectFileByNameTool.hpp | 41 ++++++
tools/ReadVisibleFilesTool.cpp | 86 +++++++++++
.../ReadVisibleFilesTool.hpp | 21 ++-
tools/ToolHandler.cpp | 106 ++++++++++++++
tools/ToolHandler.hpp | 65 +++++++++
tools/ToolsFactory.cpp | 83 +++++++++++
tools/ToolsFactory.hpp | 45 ++++++
12 files changed, 679 insertions(+), 25 deletions(-)
create mode 100644 llmcore/BaseTool.cpp
rename llmcore/{ITool.hpp => BaseTool.hpp} (60%)
create mode 100644 tools/ReadProjectFileByNameTool.cpp
create mode 100644 tools/ReadProjectFileByNameTool.hpp
create mode 100644 tools/ReadVisibleFilesTool.cpp
rename llmcore/IToolsFactory.hpp => tools/ReadVisibleFilesTool.hpp (66%)
create mode 100644 tools/ToolHandler.cpp
create mode 100644 tools/ToolHandler.hpp
create mode 100644 tools/ToolsFactory.cpp
create mode 100644 tools/ToolsFactory.hpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2c38b1e..044e313 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -111,6 +111,10 @@ add_qtc_plugin(QodeAssist
widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
+ tools/ToolsFactory.hpp tools/ToolsFactory.cpp
+ tools/ReadProjectFileByNameTool.hpp tools/ReadProjectFileByNameTool.cpp
+ tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp
+ tools/ToolHandler.hpp tools/ToolHandler.cpp
)
get_target_property(QtCreatorCorePath QtCreator::Core LOCATION)
diff --git a/llmcore/BaseTool.cpp b/llmcore/BaseTool.cpp
new file mode 100644
index 0000000..bd4a62e
--- /dev/null
+++ b/llmcore/BaseTool.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "BaseTool.hpp"
+
+namespace QodeAssist::LLMCore {
+
+BaseTool::BaseTool(QObject *parent)
+ : QObject(parent)
+{}
+
+QJsonObject BaseTool::getDefinition(ToolSchemaFormat format) const
+{
+ QJsonObject properties;
+ QJsonObject filenameProperty;
+ filenameProperty["type"] = "string";
+ filenameProperty["description"] = "The filename or relative path to read";
+ properties["filename"] = filenameProperty;
+
+ QJsonObject definition;
+ definition["type"] = "object";
+ definition["properties"] = properties;
+
+ QJsonArray required;
+ required.append("filename");
+ definition["required"] = required;
+
+ switch (format) {
+ case LLMCore::ToolSchemaFormat::OpenAI:
+ definition = customizeForOpenAI(definition);
+ break;
+ case LLMCore::ToolSchemaFormat::Claude:
+ definition = customizeForClaude(definition);
+ break;
+ case LLMCore::ToolSchemaFormat::Ollama:
+ definition = customizeForOllama(definition);
+ break;
+ }
+
+ return definition;
+}
+
+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);
+}
+
+} // namespace QodeAssist::LLMCore
diff --git a/llmcore/ITool.hpp b/llmcore/BaseTool.hpp
similarity index 60%
rename from llmcore/ITool.hpp
rename to llmcore/BaseTool.hpp
index ebe893b..6a4c6fd 100644
--- a/llmcore/ITool.hpp
+++ b/llmcore/BaseTool.hpp
@@ -19,31 +19,33 @@
#pragma once
+#include
+#include
#include
#include
#include
namespace QodeAssist::LLMCore {
-class ITool : public QObject
+enum class ToolSchemaFormat { OpenAI, Claude, Ollama };
+
+class BaseTool : public QObject
{
Q_OBJECT
-
public:
- explicit ITool(QObject *parent = nullptr)
- : QObject(parent)
- {}
- virtual ~ITool() = default;
+ explicit BaseTool(QObject *parent = nullptr);
+ ~BaseTool() override = default;
virtual QString name() const = 0;
virtual QString description() const = 0;
- virtual QJsonObject getDefinition() const = 0;
- virtual QString execute(const QJsonObject &input = QJsonObject()) = 0;
+ virtual QJsonObject getDefinition(ToolSchemaFormat format) const;
-signals:
- void toolStarted(const QString &toolName);
- void toolCompleted(const QString &toolName, const QString &result);
- void toolFailed(const QString &toolName, const QString &error);
+ virtual QFuture 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;
};
} // namespace QodeAssist::LLMCore
diff --git a/llmcore/CMakeLists.txt b/llmcore/CMakeLists.txt
index 272a44b..7b6d847 100644
--- a/llmcore/CMakeLists.txt
+++ b/llmcore/CMakeLists.txt
@@ -17,8 +17,8 @@ add_library(LLMCore STATIC
HttpClient.hpp HttpClient.cpp
DataBuffers.hpp
SSEBuffer.hpp SSEBuffer.cpp
- ITool.hpp
- IToolsFactory.hpp
+ BaseTool.hpp
+ BaseTool.cpp
)
target_link_libraries(LLMCore
diff --git a/tools/ReadProjectFileByNameTool.cpp b/tools/ReadProjectFileByNameTool.cpp
new file mode 100644
index 0000000..7776ebc
--- /dev/null
+++ b/tools/ReadProjectFileByNameTool.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "ReadProjectFileByNameTool.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace QodeAssist::Tools {
+
+ReadProjectFileByNameTool::ReadProjectFileByNameTool(QObject *parent)
+ : BaseTool(parent)
+{}
+
+QString ReadProjectFileByNameTool::name() const
+{
+ return "read_project_file_by_name";
+}
+
+QString ReadProjectFileByNameTool::description() const
+{
+ return "Read the content of a specific file from the current project by providing its filename "
+ "or "
+ "relative path. This tool searches for files within the project scope and supports:\n"
+ "- Exact filename match (e.g., 'main.cpp')\n"
+ "- Relative path from project root (e.g., 'src/utils/helper.cpp')\n"
+ "- Partial path matching (e.g., 'utils/helper.cpp')\n"
+ "- Case-insensitive filename search as fallback\n"
+ "Input parameter: 'filename' - the name or path of the file to read.\n"
+ "Use this when you need to examine specific project files that are not currently open "
+ "in the editor.";
+}
+
+QFuture ReadProjectFileByNameTool::executeAsync(const QJsonObject &input)
+{
+ return QtConcurrent::run([this, input]() -> QString {
+ QString filename = input["filename"].toString();
+ if (filename.isEmpty()) {
+ QString error = "Error: filename parameter is required";
+ throw std::invalid_argument(error.toStdString());
+ }
+
+ QString filePath = findFileInProject(filename);
+ if (filePath.isEmpty()) {
+ QString error = QString("Error: File '%1' not found").arg(filename);
+ throw std::runtime_error(error.toStdString());
+ }
+
+ QString content = readFileContent(filePath);
+ if (content.isNull()) {
+ QString error = QString("Error: Could not read file '%1'").arg(filePath);
+ throw std::runtime_error(error.toStdString());
+ }
+
+ QString result = QString("File: %1\n\nContent:\n%2").arg(filePath, content);
+ return result;
+ });
+}
+
+QString ReadProjectFileByNameTool::findFileInProject(const QString &fileName) const
+{
+ auto project = ProjectExplorer::ProjectManager::startupProject();
+ if (!project) {
+ LOG_MESSAGE("No startup project found");
+ return QString();
+ }
+
+ Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
+
+ for (const auto &projectFile : projectFiles) {
+ QFileInfo fileInfo(projectFile.path());
+ if (fileInfo.fileName() == fileName) {
+ return projectFile.path();
+ }
+ }
+
+ for (const auto &projectFile : projectFiles) {
+ if (projectFile.endsWith(fileName)) {
+ return projectFile.path();
+ }
+ }
+
+ for (const auto &projectFile : projectFiles) {
+ QFileInfo fileInfo(projectFile.path());
+ if (fileInfo.fileName().contains(fileName, Qt::CaseInsensitive)) {
+ return projectFile.path();
+ }
+ }
+
+ return QString();
+}
+
+QString ReadProjectFileByNameTool::readFileContent(const QString &filePath) const
+{
+ QFile file(filePath);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ LOG_MESSAGE(QString("Could not open file: %1").arg(filePath));
+ return QString();
+ }
+
+ QTextStream stream(&file);
+ stream.setAutoDetectUnicode(true);
+ QString content = stream.readAll();
+
+ file.close();
+ return content;
+}
+
+} // namespace QodeAssist::Tools
diff --git a/tools/ReadProjectFileByNameTool.hpp b/tools/ReadProjectFileByNameTool.hpp
new file mode 100644
index 0000000..8f40798
--- /dev/null
+++ b/tools/ReadProjectFileByNameTool.hpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include "llmcore/BaseTool.hpp"
+
+namespace QodeAssist::Tools {
+
+class ReadProjectFileByNameTool : public LLMCore::BaseTool
+{
+ Q_OBJECT
+public:
+ explicit ReadProjectFileByNameTool(QObject *parent = nullptr);
+
+ QString name() const override;
+ QString description() const override;
+ QFuture executeAsync(const QJsonObject &input = QJsonObject()) override;
+
+private:
+ QString findFileInProject(const QString &fileName) const;
+ QString readFileContent(const QString &filePath) const;
+};
+
+} // namespace QodeAssist::Tools
diff --git a/tools/ReadVisibleFilesTool.cpp b/tools/ReadVisibleFilesTool.cpp
new file mode 100644
index 0000000..035120b
--- /dev/null
+++ b/tools/ReadVisibleFilesTool.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "ReadVisibleFilesTool.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace QodeAssist::Tools {
+
+ReadVisibleFilesTool::ReadVisibleFilesTool(QObject *parent)
+ : BaseTool(parent)
+{}
+
+QString ReadVisibleFilesTool::name() const
+{
+ return "read_visible_files";
+}
+
+QString ReadVisibleFilesTool::description() const
+{
+ return "Read the content of all currently visible files in editor tabs. "
+ "Returns content from all open tabs that are currently visible, including unsaved "
+ "changes. "
+ "No parameters required.";
+}
+
+QFuture ReadVisibleFilesTool::executeAsync(const QJsonObject &input)
+{
+ Q_UNUSED(input)
+
+ return QtConcurrent::run([this]() -> QString {
+ auto editors = Core::EditorManager::visibleEditors();
+
+ if (editors.isEmpty()) {
+ QString error = "Error: No visible files in the editor";
+ throw std::runtime_error(error.toStdString());
+ }
+
+ QStringList results;
+
+ for (auto editor : editors) {
+ if (!editor || !editor->document()) {
+ continue;
+ }
+
+ QString filePath = editor->document()->filePath().toFSPathString();
+ QByteArray contentBytes = editor->document()->contents();
+ QString fileContent = QString::fromUtf8(contentBytes);
+
+ QString fileResult;
+ if (fileContent.isEmpty()) {
+ fileResult
+ = QString("File: %1\n\nThe file is empty or could not be read").arg(filePath);
+ } else {
+ fileResult = QString("File: %1\n\nContent:\n%2").arg(filePath, fileContent);
+ }
+
+ results.append(fileResult);
+ }
+
+ return results.join("\n\n" + QString(80, '=') + "\n\n");
+ });
+}
+
+} // namespace QodeAssist::Tools
diff --git a/llmcore/IToolsFactory.hpp b/tools/ReadVisibleFilesTool.hpp
similarity index 66%
rename from llmcore/IToolsFactory.hpp
rename to tools/ReadVisibleFilesTool.hpp
index 8f47c13..584e99d 100644
--- a/llmcore/IToolsFactory.hpp
+++ b/tools/ReadVisibleFilesTool.hpp
@@ -19,20 +19,19 @@
#pragma once
-#include
-#include
+#include "llmcore/BaseTool.hpp"
-#include "ITool.hpp"
+namespace QodeAssist::Tools {
-namespace QodeAssist::LLMCore {
-
-class IToolsFactory
+class ReadVisibleFilesTool : public LLMCore::BaseTool
{
+ Q_OBJECT
public:
- virtual ~IToolsFactory() = default;
- virtual QList getAvailableTools() const = 0;
- virtual ITool *getToolByName(const QString &name) const = 0;
- virtual QJsonArray getToolsDefinitions() const = 0;
+ explicit ReadVisibleFilesTool(QObject *parent = nullptr);
+
+ QString name() const override;
+ QString description() const override;
+ QFuture executeAsync(const QJsonObject &input = QJsonObject()) override;
};
-} // namespace QodeAssist::LLMCore
+} // namespace QodeAssist::Tools
diff --git a/tools/ToolHandler.cpp b/tools/ToolHandler.cpp
new file mode 100644
index 0000000..e16c887
--- /dev/null
+++ b/tools/ToolHandler.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "ToolHandler.hpp"
+
+#include
+#include
+#include
+
+#include "logger/Logger.hpp"
+
+namespace QodeAssist::Tools {
+
+ToolHandler::ToolHandler(QObject *parent)
+ : QObject(parent)
+{}
+
+QFuture ToolHandler::executeToolAsync(
+ const QString &requestId,
+ const QString &toolId,
+ LLMCore::BaseTool *tool,
+ const QJsonObject &input)
+{
+ if (!tool) {
+ return QtConcurrent::run([]() -> QString { throw std::runtime_error("Tool is null"); });
+ }
+
+ auto execution = new ToolExecution();
+ execution->requestId = requestId;
+ execution->toolId = toolId;
+ execution->toolName = tool->name();
+ execution->watcher = new QFutureWatcher(this);
+
+ connect(execution->watcher, &QFutureWatcher::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);
+
+ 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 std::exception &e) {
+ QString error = QString::fromStdString(e.what());
+ 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
diff --git a/tools/ToolHandler.hpp b/tools/ToolHandler.hpp
new file mode 100644
index 0000000..c8b2367
--- /dev/null
+++ b/tools/ToolHandler.hpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace QodeAssist::Tools {
+
+class ToolHandler : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ToolHandler(QObject *parent = nullptr);
+
+ QFuture executeToolAsync(
+ const QString &requestId,
+ const QString &toolId,
+ LLMCore::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 *watcher;
+ };
+
+ QHash m_activeExecutions; // toolId -> execution
+
+ void onToolExecutionFinished(const QString &toolId);
+};
+
+} // namespace QodeAssist::Tools
diff --git a/tools/ToolsFactory.cpp b/tools/ToolsFactory.cpp
new file mode 100644
index 0000000..50e98c7
--- /dev/null
+++ b/tools/ToolsFactory.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "ToolsFactory.hpp"
+
+#include "logger/Logger.hpp"
+#include
+#include
+
+#include "ReadVisibleFilesTool.hpp"
+#include "ReadProjectFileByNameTool.hpp"
+
+namespace QodeAssist::Tools {
+
+ToolsFactory::ToolsFactory(QObject *parent)
+ : QObject(parent)
+{
+ registerTools();
+}
+
+void ToolsFactory::registerTools()
+{
+ registerTool(new ReadVisibleFilesTool(this));
+ registerTool(new ReadProjectFileByNameTool(this));
+
+ LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size()));
+}
+
+void ToolsFactory::registerTool(LLMCore::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 ToolsFactory::getAvailableTools() const
+{
+ return m_tools.values();
+}
+
+LLMCore::BaseTool *ToolsFactory::getToolByName(const QString &name) const
+{
+ return m_tools.value(name, nullptr);
+}
+
+QJsonArray ToolsFactory::getToolsDefinitions(LLMCore::ToolSchemaFormat format) const
+{
+ QJsonArray toolsArray;
+
+ for (auto it = m_tools.constBegin(); it != m_tools.constEnd(); ++it) {
+ if (it.value()) {
+ toolsArray.append(it.value()->getDefinition(format));
+ }
+ }
+
+ return toolsArray;
+}
+
+} // namespace QodeAssist::Tools
diff --git a/tools/ToolsFactory.hpp b/tools/ToolsFactory.hpp
new file mode 100644
index 0000000..14663a0
--- /dev/null
+++ b/tools/ToolsFactory.hpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include
+
+#include
+
+namespace QodeAssist::Tools {
+
+class ToolsFactory : public QObject
+{
+ Q_OBJECT
+public:
+ ToolsFactory(QObject *parent = nullptr);
+ ~ToolsFactory() override = default;
+
+ QList getAvailableTools() const;
+ LLMCore::BaseTool *getToolByName(const QString &name) const;
+ QJsonArray getToolsDefinitions(LLMCore::ToolSchemaFormat format) const;
+
+private:
+ void registerTools();
+ void registerTool(LLMCore::BaseTool *tool);
+
+ QHash m_tools;
+};
+} // namespace QodeAssist::Tools