From ff750c271aa32d5cdd6a47fde2011dde36e77636 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:37:40 +0200 Subject: [PATCH] feat: Add list project files tool --- CMakeLists.txt | 1 + llmcore/BaseTool.cpp | 31 ------- llmcore/BaseTool.hpp | 2 +- llmcore/Provider.hpp | 2 + tools/ListProjectFilesTool.cpp | 129 ++++++++++++++++++++++++++++ tools/ListProjectFilesTool.hpp | 41 +++++++++ tools/ReadProjectFileByNameTool.cpp | 67 +++++++++++---- tools/ReadProjectFileByNameTool.hpp | 1 + tools/ReadVisibleFilesTool.cpp | 19 ++++ tools/ReadVisibleFilesTool.hpp | 1 + tools/ToolsFactory.cpp | 4 +- 11 files changed, 248 insertions(+), 50 deletions(-) create mode 100644 tools/ListProjectFilesTool.cpp create mode 100644 tools/ListProjectFilesTool.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 044e313..209c377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ add_qtc_plugin(QodeAssist tools/ReadProjectFileByNameTool.hpp tools/ReadProjectFileByNameTool.cpp tools/ReadVisibleFilesTool.hpp tools/ReadVisibleFilesTool.cpp tools/ToolHandler.hpp tools/ToolHandler.cpp + tools/ListProjectFilesTool.hpp tools/ListProjectFilesTool.cpp ) get_target_property(QtCreatorCorePath QtCreator::Core LOCATION) diff --git a/llmcore/BaseTool.cpp b/llmcore/BaseTool.cpp index bd4a62e..1f7c559 100644 --- a/llmcore/BaseTool.cpp +++ b/llmcore/BaseTool.cpp @@ -25,37 +25,6 @@ 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; diff --git a/llmcore/BaseTool.hpp b/llmcore/BaseTool.hpp index 6a4c6fd..d01e45c 100644 --- a/llmcore/BaseTool.hpp +++ b/llmcore/BaseTool.hpp @@ -38,7 +38,7 @@ public: virtual QString name() const = 0; virtual QString description() const = 0; - virtual QJsonObject getDefinition(ToolSchemaFormat format) const; + virtual QJsonObject getDefinition(ToolSchemaFormat format) const = 0; virtual QFuture executeAsync(const QJsonObject &input = QJsonObject()) = 0; diff --git a/llmcore/Provider.hpp b/llmcore/Provider.hpp index 19f0c11..8e11ab5 100644 --- a/llmcore/Provider.hpp +++ b/llmcore/Provider.hpp @@ -63,6 +63,8 @@ public: virtual void sendRequest(const RequestID &requestId, const QUrl &url, const QJsonObject &payload) = 0; + virtual bool supportsTools() { return false; }; + HttpClient *httpClient() const; public slots: diff --git a/tools/ListProjectFilesTool.cpp b/tools/ListProjectFilesTool.cpp new file mode 100644 index 0000000..0f085fe --- /dev/null +++ b/tools/ListProjectFilesTool.cpp @@ -0,0 +1,129 @@ +/* + * 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 "ListProjectFilesTool.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QodeAssist::Tools { + +ListProjectFilesTool::ListProjectFilesTool(QObject *parent) + : BaseTool(parent) +{} + +QString ListProjectFilesTool::name() const +{ + return "list_project_files"; +} + +QString ListProjectFilesTool::description() const +{ + return "Get a list of all source files in the current project. " + "Returns a structured list of files with their relative paths from the project root. " + "Useful for understanding project structure and finding specific files. " + "No parameters required."; +} + +QJsonObject ListProjectFilesTool::getDefinition(LLMCore::ToolSchemaFormat format) const +{ + QJsonObject definition; + definition["type"] = "object"; + definition["properties"] = QJsonObject(); + definition["required"] = QJsonArray(); + + switch (format) { + case LLMCore::ToolSchemaFormat::OpenAI: + return customizeForOpenAI(definition); + case LLMCore::ToolSchemaFormat::Claude: + return customizeForClaude(definition); + case LLMCore::ToolSchemaFormat::Ollama: + return customizeForOllama(definition); + } + + return definition; +} + +QFuture ListProjectFilesTool::executeAsync(const QJsonObject &input) +{ + Q_UNUSED(input) + + return QtConcurrent::run([this]() -> QString { + QList projects = ProjectExplorer::ProjectManager::projects(); + if (projects.isEmpty()) { + QString error = "Error: No projects found"; + throw std::runtime_error(error.toStdString()); + } + + QString result; + + for (auto project : projects) { + if (!project) + continue; + + Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles); + + if (projectFiles.isEmpty()) { + result += QString("Project '%1': No source files found\n\n") + .arg(project->displayName()); + continue; + } + + QStringList fileList; + QString projectPath = project->projectDirectory().toString(); + + for (const auto &filePath : projectFiles) { + QString absolutePath = filePath.toString(); + QString relativePath = QDir(projectPath).relativeFilePath(absolutePath); + fileList.append(relativePath); + } + + fileList.sort(); + + result += QString("Project '%1' (%2 files):\n") + .arg(project->displayName()) + .arg(fileList.size()); + for (const QString &file : fileList) { + result += QString("- %1\n").arg(file); + } + result += "\n"; + } + + return result.trimmed(); + }); +} + +QString ListProjectFilesTool::formatFileList(const QStringList &files) const +{ + QString result = QString("Project files (%1 total):\n\n").arg(files.size()); + + for (const QString &file : files) { + result += QString("- %1\n").arg(file); + } + + return result; +} + +} // namespace QodeAssist::Tools diff --git a/tools/ListProjectFilesTool.hpp b/tools/ListProjectFilesTool.hpp new file mode 100644 index 0000000..360e40c --- /dev/null +++ b/tools/ListProjectFilesTool.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 ListProjectFilesTool : public LLMCore::BaseTool +{ + Q_OBJECT +public: + explicit ListProjectFilesTool(QObject *parent = nullptr); + + QString name() const override; + QString description() const override; + QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override; + QFuture executeAsync(const QJsonObject &input = QJsonObject()) override; + +private: + QString formatFileList(const QStringList &files) const; +}; + +} // namespace QodeAssist::Tools diff --git a/tools/ReadProjectFileByNameTool.cpp b/tools/ReadProjectFileByNameTool.cpp index 7776ebc..60e23a4 100644 --- a/tools/ReadProjectFileByNameTool.cpp +++ b/tools/ReadProjectFileByNameTool.cpp @@ -57,6 +57,34 @@ QString ReadProjectFileByNameTool::description() const "in the editor."; } +QJsonObject ReadProjectFileByNameTool::getDefinition(LLMCore::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: + return customizeForOpenAI(definition); + case LLMCore::ToolSchemaFormat::Claude: + return customizeForClaude(definition); + case LLMCore::ToolSchemaFormat::Ollama: + return customizeForOllama(definition); + } + + return definition; +} + QFuture ReadProjectFileByNameTool::executeAsync(const QJsonObject &input) { return QtConcurrent::run([this, input]() -> QString { @@ -85,31 +113,36 @@ QFuture ReadProjectFileByNameTool::executeAsync(const QJsonObject &inpu QString ReadProjectFileByNameTool::findFileInProject(const QString &fileName) const { - auto project = ProjectExplorer::ProjectManager::startupProject(); - if (!project) { - LOG_MESSAGE("No startup project found"); + QList projects = ProjectExplorer::ProjectManager::projects(); + if (projects.isEmpty()) { + LOG_MESSAGE("No projects found"); return QString(); } - Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles); + for (auto project : projects) { + if (!project) + continue; - for (const auto &projectFile : projectFiles) { - QFileInfo fileInfo(projectFile.path()); - if (fileInfo.fileName() == fileName) { - return projectFile.path(); + Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles); + + for (const auto &projectFile : std::as_const(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 : std::as_const(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(); + for (const auto &projectFile : std::as_const(projectFiles)) { + QFileInfo fileInfo(projectFile.path()); + if (fileInfo.fileName().contains(fileName, Qt::CaseInsensitive)) { + return projectFile.path(); + } } } diff --git a/tools/ReadProjectFileByNameTool.hpp b/tools/ReadProjectFileByNameTool.hpp index 8f40798..5c591e5 100644 --- a/tools/ReadProjectFileByNameTool.hpp +++ b/tools/ReadProjectFileByNameTool.hpp @@ -31,6 +31,7 @@ public: QString name() const override; QString description() const override; + QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override; QFuture executeAsync(const QJsonObject &input = QJsonObject()) override; private: diff --git a/tools/ReadVisibleFilesTool.cpp b/tools/ReadVisibleFilesTool.cpp index 035120b..4a6bf55 100644 --- a/tools/ReadVisibleFilesTool.cpp +++ b/tools/ReadVisibleFilesTool.cpp @@ -45,6 +45,25 @@ QString ReadVisibleFilesTool::description() const "No parameters required."; } +QJsonObject ReadVisibleFilesTool::getDefinition(LLMCore::ToolSchemaFormat format) const +{ + QJsonObject definition; + definition["type"] = "object"; + definition["properties"] = QJsonObject(); + definition["required"] = QJsonArray(); + + switch (format) { + case LLMCore::ToolSchemaFormat::OpenAI: + return customizeForOpenAI(definition); + case LLMCore::ToolSchemaFormat::Claude: + return customizeForClaude(definition); + case LLMCore::ToolSchemaFormat::Ollama: + return customizeForOllama(definition); + } + + return definition; +} + QFuture ReadVisibleFilesTool::executeAsync(const QJsonObject &input) { Q_UNUSED(input) diff --git a/tools/ReadVisibleFilesTool.hpp b/tools/ReadVisibleFilesTool.hpp index 584e99d..f471c0b 100644 --- a/tools/ReadVisibleFilesTool.hpp +++ b/tools/ReadVisibleFilesTool.hpp @@ -31,6 +31,7 @@ public: QString name() const override; QString description() const override; + QJsonObject getDefinition(LLMCore::ToolSchemaFormat format) const override; QFuture executeAsync(const QJsonObject &input = QJsonObject()) override; }; diff --git a/tools/ToolsFactory.cpp b/tools/ToolsFactory.cpp index 50e98c7..4dc021f 100644 --- a/tools/ToolsFactory.cpp +++ b/tools/ToolsFactory.cpp @@ -23,8 +23,9 @@ #include #include -#include "ReadVisibleFilesTool.hpp" +#include "ListProjectFilesTool.hpp" #include "ReadProjectFileByNameTool.hpp" +#include "ReadVisibleFilesTool.hpp" namespace QodeAssist::Tools { @@ -38,6 +39,7 @@ void ToolsFactory::registerTools() { registerTool(new ReadVisibleFilesTool(this)); registerTool(new ReadProjectFileByNameTool(this)); + registerTool(new ListProjectFilesTool(this)); LOG_MESSAGE(QString("Registered %1 tools").arg(m_tools.size())); }