mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 02:49:12 -04:00
refactor: Separate and simplified tools (#340)
This commit is contained in:
@@ -151,7 +151,8 @@ add_qtc_plugin(QodeAssist
|
||||
tools/BuildProjectTool.hpp tools/BuildProjectTool.cpp
|
||||
tools/ExecuteTerminalCommandTool.hpp tools/ExecuteTerminalCommandTool.cpp
|
||||
tools/ProjectSearchTool.hpp tools/ProjectSearchTool.cpp
|
||||
tools/FindAndReadFileTool.hpp tools/FindAndReadFileTool.cpp
|
||||
tools/FindFileTool.hpp tools/FindFileTool.cpp
|
||||
tools/ReadFileTool.hpp tools/ReadFileTool.cpp
|
||||
tools/FileSearchUtils.hpp tools/FileSearchUtils.cpp
|
||||
tools/TodoTool.hpp tools/TodoTool.cpp
|
||||
)
|
||||
|
||||
@@ -99,14 +99,17 @@ const char CA_ENABLE_CHAT_IN_BOTTOM_TOOLBAR[] = "QodeAssist.caEnableChatInBottom
|
||||
const char CA_ENABLE_CHAT_IN_NAVIGATION_PANEL[] = "QodeAssist.caEnableChatInNavigationPanel";
|
||||
const char CA_ENABLE_CHAT_TOOLS[] = "QodeAssist.caEnableChatTools";
|
||||
const char CA_USE_TOOLS[] = "QodeAssist.caUseTools";
|
||||
const char CA_ALLOW_FILE_SYSTEM_READ[] = "QodeAssist.caAllowFileSystemRead";
|
||||
const char CA_ALLOW_FILE_SYSTEM_WRITE[] = "QodeAssist.caAllowFileSystemWrite";
|
||||
const char CA_ALLOW_NETWORK_ACCESS[] = "QodeAssist.caAllowNetworkAccess";
|
||||
const char CA_ALLOW_ACCESS_OUTSIDE_PROJECT[] = "QodeAssist.caAllowAccessOutsideProject";
|
||||
const char CA_ENABLE_EDIT_FILE_TOOL[] = "QodeAssist.caEnableEditFileTool";
|
||||
const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectTool";
|
||||
const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandTool";
|
||||
const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoTool";
|
||||
const char CA_ENABLE_LIST_PROJECT_FILES_TOOL[] = "QodeAssist.caEnableListProjectFilesTool";
|
||||
const char CA_ENABLE_FIND_FILE_TOOL[] = "QodeAssist.caEnableFindFileTool";
|
||||
const char CA_ENABLE_READ_FILE_TOOL[] = "QodeAssist.caEnableReadFileTool";
|
||||
const char CA_ENABLE_PROJECT_SEARCH_TOOL[] = "QodeAssist.caEnableProjectSearchTool";
|
||||
const char CA_ENABLE_CREATE_NEW_FILE_TOOL[] = "QodeAssist.caEnableCreateNewFileTool";
|
||||
const char CA_ENABLE_GET_ISSUES_LIST_TOOL[] = "QodeAssist.caEnableGetIssuesListTool";
|
||||
const char CA_ENABLE_EDIT_FILE_TOOL[] = "QodeAssist.caEnableEditFileToolV2";
|
||||
const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectToolV2";
|
||||
const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandToolV2";
|
||||
const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoToolV2";
|
||||
const char CA_ALLOWED_TERMINAL_COMMANDS[] = "QodeAssist.caAllowedTerminalCommands";
|
||||
const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux";
|
||||
const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
@@ -42,66 +42,80 @@ ToolsSettings::ToolsSettings()
|
||||
|
||||
setDisplayName(Tr::tr("Tools"));
|
||||
|
||||
allowFileSystemRead.setSettingsKey(Constants::CA_ALLOW_FILE_SYSTEM_READ);
|
||||
allowFileSystemRead.setLabelText(Tr::tr("Allow File System Read Access for tools"));
|
||||
allowFileSystemRead.setToolTip(
|
||||
Tr::tr("Allow tools to read files from disk (project files, open editors)"));
|
||||
allowFileSystemRead.setDefaultValue(true);
|
||||
|
||||
allowFileSystemWrite.setSettingsKey(Constants::CA_ALLOW_FILE_SYSTEM_WRITE);
|
||||
allowFileSystemWrite.setLabelText(Tr::tr("Allow File System Write Access for tools"));
|
||||
allowFileSystemWrite.setToolTip(
|
||||
Tr::tr("Allow tools to write and modify files on disk (WARNING: Use with caution!)"));
|
||||
allowFileSystemWrite.setDefaultValue(false);
|
||||
|
||||
allowNetworkAccess.setSettingsKey(Constants::CA_ALLOW_NETWORK_ACCESS);
|
||||
allowNetworkAccess.setLabelText(Tr::tr("Allow Network Access for tools"));
|
||||
allowNetworkAccess.setToolTip(
|
||||
Tr::tr("Allow tools to make network requests (e.g., execute commands like git, curl, wget). "
|
||||
"Required for ExecuteTerminalCommandTool with network-capable commands."));
|
||||
allowNetworkAccess.setDefaultValue(false);
|
||||
|
||||
allowAccessOutsideProject.setSettingsKey(Constants::CA_ALLOW_ACCESS_OUTSIDE_PROJECT);
|
||||
allowAccessOutsideProject.setLabelText(Tr::tr("Allow file access outside project"));
|
||||
allowAccessOutsideProject.setToolTip(
|
||||
Tr::tr("Allow tools to access (read/write) files outside the project scope (system "
|
||||
"headers, Qt files, external libraries)"));
|
||||
allowAccessOutsideProject.setDefaultValue(true);
|
||||
Tr::tr("Allow tools to read, write, and create files outside the project scope "
|
||||
"(system headers, Qt files, external libraries)."));
|
||||
allowAccessOutsideProject.setDefaultValue(false);
|
||||
|
||||
autoApplyFileEdits.setSettingsKey(Constants::CA_AUTO_APPLY_FILE_EDITS);
|
||||
autoApplyFileEdits.setLabelText(Tr::tr("Automatically apply file edits"));
|
||||
autoApplyFileEdits.setToolTip(
|
||||
Tr::tr("When enabled, file edits suggested by AI will be applied automatically. "
|
||||
"When disabled, you will need to manually approve each edit."));
|
||||
Tr::tr("When enabled, file edits suggested by AI are applied immediately. "
|
||||
"When disabled, each edit is staged for manual approval."));
|
||||
autoApplyFileEdits.setDefaultValue(false);
|
||||
|
||||
enableListProjectFilesTool.setSettingsKey(Constants::CA_ENABLE_LIST_PROJECT_FILES_TOOL);
|
||||
enableListProjectFilesTool.setLabelText(Tr::tr("List Project Files"));
|
||||
enableListProjectFilesTool.setToolTip(
|
||||
Tr::tr("Lists every source file tracked by the active Qt Creator project(s)."));
|
||||
enableListProjectFilesTool.setDefaultValue(true);
|
||||
|
||||
enableFindFileTool.setSettingsKey(Constants::CA_ENABLE_FIND_FILE_TOOL);
|
||||
enableFindFileTool.setLabelText(Tr::tr("Find File"));
|
||||
enableFindFileTool.setToolTip(
|
||||
Tr::tr("Locates a file in the project by name or partial path. Returns paths only, "
|
||||
"without file content."));
|
||||
enableFindFileTool.setDefaultValue(true);
|
||||
|
||||
enableReadFileTool.setSettingsKey(Constants::CA_ENABLE_READ_FILE_TOOL);
|
||||
enableReadFileTool.setLabelText(Tr::tr("Read File"));
|
||||
enableReadFileTool.setToolTip(
|
||||
Tr::tr("Reads the content of a file by absolute path or path relative to the project root."));
|
||||
enableReadFileTool.setDefaultValue(true);
|
||||
|
||||
enableProjectSearchTool.setSettingsKey(Constants::CA_ENABLE_PROJECT_SEARCH_TOOL);
|
||||
enableProjectSearchTool.setLabelText(Tr::tr("Search in Project"));
|
||||
enableProjectSearchTool.setToolTip(
|
||||
Tr::tr("Searches project files for text occurrences or C++ symbol definitions."));
|
||||
enableProjectSearchTool.setDefaultValue(true);
|
||||
|
||||
enableCreateNewFileTool.setSettingsKey(Constants::CA_ENABLE_CREATE_NEW_FILE_TOOL);
|
||||
enableCreateNewFileTool.setLabelText(Tr::tr("Create New File"));
|
||||
enableCreateNewFileTool.setToolTip(
|
||||
Tr::tr("Creates a new empty file at the given absolute path, making missing directories."));
|
||||
enableCreateNewFileTool.setDefaultValue(true);
|
||||
|
||||
enableEditFileTool.setSettingsKey(Constants::CA_ENABLE_EDIT_FILE_TOOL);
|
||||
enableEditFileTool.setLabelText(Tr::tr("Enable Edit File Tool (Experimental)"));
|
||||
enableEditFileTool.setLabelText(Tr::tr("Edit File"));
|
||||
enableEditFileTool.setToolTip(
|
||||
Tr::tr("Enable the experimental edit_file tool that allows AI to directly modify files. "
|
||||
"This feature is under testing and may have unexpected behavior."));
|
||||
enableEditFileTool.setDefaultValue(false);
|
||||
Tr::tr("Applies find-and-replace edits to files. See \"Automatically apply file edits\" "
|
||||
"to control whether edits apply immediately or wait for review."));
|
||||
enableEditFileTool.setDefaultValue(true);
|
||||
|
||||
enableBuildProjectTool.setSettingsKey(Constants::CA_ENABLE_BUILD_PROJECT_TOOL);
|
||||
enableBuildProjectTool.setLabelText(Tr::tr("Enable Build Project Tool (Experimental)"));
|
||||
enableBuildProjectTool.setLabelText(Tr::tr("Build Project"));
|
||||
enableBuildProjectTool.setToolTip(
|
||||
Tr::tr("Enable the experimental build_project tool that allows AI to build the current "
|
||||
"project. This feature is under testing and may have unexpected behavior."));
|
||||
enableBuildProjectTool.setDefaultValue(false);
|
||||
Tr::tr("Triggers a build of the active Qt Creator project and reports the result."));
|
||||
enableBuildProjectTool.setDefaultValue(true);
|
||||
|
||||
enableGetIssuesListTool.setSettingsKey(Constants::CA_ENABLE_GET_ISSUES_LIST_TOOL);
|
||||
enableGetIssuesListTool.setLabelText(Tr::tr("Get Issues List"));
|
||||
enableGetIssuesListTool.setToolTip(
|
||||
Tr::tr("Reads compiler/clang diagnostics from Qt Creator's Issues panel."));
|
||||
enableGetIssuesListTool.setDefaultValue(true);
|
||||
|
||||
enableTerminalCommandTool.setSettingsKey(Constants::CA_ENABLE_TERMINAL_COMMAND_TOOL);
|
||||
enableTerminalCommandTool.setLabelText(Tr::tr("Enable Terminal Command Tool (Experimental)"));
|
||||
enableTerminalCommandTool.setLabelText(Tr::tr("Execute Terminal Command"));
|
||||
enableTerminalCommandTool.setToolTip(
|
||||
Tr::tr("Enable the experimental execute_terminal_command tool that allows AI to execute "
|
||||
"terminal commands from the allowed list. This feature is under testing and may have "
|
||||
"unexpected behavior."));
|
||||
enableTerminalCommandTool.setDefaultValue(false);
|
||||
Tr::tr("Runs a command from the OS-specific allowed list below, in the project directory."));
|
||||
enableTerminalCommandTool.setDefaultValue(true);
|
||||
|
||||
enableTodoTool.setSettingsKey(Constants::CA_ENABLE_TODO_TOOL);
|
||||
enableTodoTool.setLabelText(Tr::tr("Enable Todo Tool"));
|
||||
enableTodoTool.setLabelText(Tr::tr("Todo"));
|
||||
enableTodoTool.setToolTip(
|
||||
Tr::tr("Enable the todo_tool that helps AI track and organize multi-step tasks. "
|
||||
"Useful for complex refactoring, debugging, and feature implementation workflows."));
|
||||
Tr::tr("Lets the AI maintain a session-scoped todo list for multi-step workflows."));
|
||||
enableTodoTool.setDefaultValue(true);
|
||||
|
||||
allowedTerminalCommandsLinux.setSettingsKey(Constants::CA_ALLOWED_TERMINAL_COMMANDS_LINUX);
|
||||
@@ -159,24 +173,30 @@ ToolsSettings::ToolsSettings()
|
||||
Row{Stretch{1}, resetToDefaults},
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("Tool Settings")),
|
||||
Column{
|
||||
allowFileSystemRead,
|
||||
allowFileSystemWrite,
|
||||
allowNetworkAccess,
|
||||
allowAccessOutsideProject
|
||||
}},
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("Experimental Features")),
|
||||
title(Tr::tr("Tools")),
|
||||
Column{
|
||||
enableListProjectFilesTool,
|
||||
enableFindFileTool,
|
||||
enableReadFileTool,
|
||||
enableProjectSearchTool,
|
||||
enableCreateNewFileTool,
|
||||
enableEditFileTool,
|
||||
enableBuildProjectTool,
|
||||
enableGetIssuesListTool,
|
||||
enableTerminalCommandTool,
|
||||
enableTodoTool,
|
||||
currentOsCommands,
|
||||
terminalCommandTimeout,
|
||||
autoApplyFileEdits}},
|
||||
enableTodoTool}},
|
||||
Space{8},
|
||||
Group{
|
||||
title(Tr::tr("Tool Settings")),
|
||||
Column{
|
||||
allowAccessOutsideProject,
|
||||
Space{4},
|
||||
Group{
|
||||
title(Tr::tr("Edit File")),
|
||||
Column{autoApplyFileEdits}},
|
||||
Group{
|
||||
title(Tr::tr("Execute Terminal Command")),
|
||||
Column{currentOsCommands, terminalCommandTimeout}}}},
|
||||
Stretch{1}};
|
||||
});
|
||||
}
|
||||
@@ -200,13 +220,16 @@ void ToolsSettings::resetSettingsToDefaults()
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
resetAspect(allowFileSystemRead);
|
||||
resetAspect(allowFileSystemWrite);
|
||||
resetAspect(allowNetworkAccess);
|
||||
resetAspect(allowAccessOutsideProject);
|
||||
resetAspect(autoApplyFileEdits);
|
||||
resetAspect(enableListProjectFilesTool);
|
||||
resetAspect(enableFindFileTool);
|
||||
resetAspect(enableReadFileTool);
|
||||
resetAspect(enableProjectSearchTool);
|
||||
resetAspect(enableCreateNewFileTool);
|
||||
resetAspect(enableEditFileTool);
|
||||
resetAspect(enableBuildProjectTool);
|
||||
resetAspect(enableGetIssuesListTool);
|
||||
resetAspect(enableTerminalCommandTool);
|
||||
resetAspect(enableTodoTool);
|
||||
resetAspect(allowedTerminalCommandsLinux);
|
||||
|
||||
@@ -32,21 +32,24 @@ public:
|
||||
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
Utils::BoolAspect allowFileSystemRead{this};
|
||||
Utils::BoolAspect allowFileSystemWrite{this};
|
||||
Utils::BoolAspect allowNetworkAccess{this};
|
||||
Utils::BoolAspect allowAccessOutsideProject{this};
|
||||
Utils::BoolAspect autoApplyFileEdits{this};
|
||||
|
||||
// Experimental features
|
||||
Utils::BoolAspect enableListProjectFilesTool{this};
|
||||
Utils::BoolAspect enableFindFileTool{this};
|
||||
Utils::BoolAspect enableReadFileTool{this};
|
||||
Utils::BoolAspect enableProjectSearchTool{this};
|
||||
Utils::BoolAspect enableCreateNewFileTool{this};
|
||||
Utils::BoolAspect enableEditFileTool{this};
|
||||
Utils::BoolAspect enableBuildProjectTool{this};
|
||||
Utils::BoolAspect enableGetIssuesListTool{this};
|
||||
Utils::BoolAspect enableTerminalCommandTool{this};
|
||||
Utils::BoolAspect enableTodoTool{this};
|
||||
|
||||
Utils::StringAspect allowedTerminalCommandsLinux{this};
|
||||
Utils::StringAspect allowedTerminalCommandsMacOS{this};
|
||||
Utils::StringAspect allowedTerminalCommandsWindows{this};
|
||||
Utils::IntegerAspect terminalCommandTimeout{this};
|
||||
Utils::BoolAspect autoApplyFileEdits{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
|
||||
@@ -71,13 +71,10 @@ QString BuildProjectTool::displayName() const
|
||||
|
||||
QString BuildProjectTool::description() const
|
||||
{
|
||||
return "Build the current project in Qt Creator and wait for completion. "
|
||||
"Optionally run the project after successful build. "
|
||||
"Returns build status (success/failure) and any compilation errors/warnings after "
|
||||
"the build finishes. "
|
||||
"Optional 'rebuild' parameter: set to true to force a clean rebuild (default: false). "
|
||||
"Optional 'run_after_build' parameter: set to true to run the project after successful build (default: false). "
|
||||
"Note: This operation may take some time depending on project size.";
|
||||
return "Build the active Qt Creator project using its current build configuration and block "
|
||||
"until the build finishes. Returns success/failure along with the full compiler output "
|
||||
"(stdout + stderr). Use `get_issues_list` afterwards to get structured diagnostics. "
|
||||
"This call is blocking and may take a long time for large projects.";
|
||||
}
|
||||
|
||||
QJsonObject BuildProjectTool::parametersSchema() const
|
||||
|
||||
@@ -49,10 +49,10 @@ QString CreateNewFileTool::displayName() const
|
||||
|
||||
QString CreateNewFileTool::description() const
|
||||
{
|
||||
return "Create a new empty file at the specified path. "
|
||||
"If the directory path does not exist, it will be created automatically. "
|
||||
"Provide absolute file path. After creating files, add the file "
|
||||
"to the project file";
|
||||
return "Create a new empty file at the given absolute path. Any missing parent directories "
|
||||
"are created automatically. The file is written to disk only — it is NOT added to the "
|
||||
"project's build system automatically; the user must register it in CMakeLists.txt or "
|
||||
"the equivalent project file. Use `edit_file` afterwards to populate its content.";
|
||||
}
|
||||
|
||||
QJsonObject CreateNewFileTool::parametersSchema() const
|
||||
@@ -61,7 +61,9 @@ QJsonObject CreateNewFileTool::parametersSchema() const
|
||||
|
||||
QJsonObject filepathProperty;
|
||||
filepathProperty["type"] = "string";
|
||||
filepathProperty["description"] = "The absolute path where the new file should be created";
|
||||
filepathProperty["description"]
|
||||
= "Absolute path where the new file should be created. Parent directories are made if "
|
||||
"missing. Relative paths are rejected.";
|
||||
properties["filepath"] = filepathProperty;
|
||||
|
||||
QJsonObject definition;
|
||||
|
||||
@@ -57,7 +57,7 @@ QString EditFileTool::description() const
|
||||
"and new_content to replace it with. Changes are applied immediately if auto-apply "
|
||||
"is enabled in settings. The user can undo or reapply changes at any time. "
|
||||
"\n\nIMPORTANT:"
|
||||
"\n- ALWAYS read the current file content before editing to ensure accuracy."
|
||||
"\n- ALWAYS use read_file to get current file content before editing to ensure accuracy."
|
||||
"\n- Path can be absolute (e.g., /path/to/file.cpp) or relative to project root (e.g., src/main.cpp)."
|
||||
"\n- For EMPTY files: use empty old_content (empty string or omit parameter)."
|
||||
"\n- To append at the END of file: use empty old_content."
|
||||
|
||||
@@ -1,125 +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 "FindAndReadFileTool.hpp"
|
||||
|
||||
#include <LLMQore/ToolExceptions.hpp>
|
||||
|
||||
#include <logger/Logger.hpp>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
FindAndReadFileTool::FindAndReadFileTool(QObject *parent)
|
||||
: BaseTool(parent)
|
||||
, m_ignoreManager(new Context::IgnoreManager(this))
|
||||
{}
|
||||
|
||||
QString FindAndReadFileTool::id() const
|
||||
{
|
||||
return "find_and_read_file";
|
||||
}
|
||||
|
||||
QString FindAndReadFileTool::displayName() const
|
||||
{
|
||||
return "Finding and reading file";
|
||||
}
|
||||
|
||||
QString FindAndReadFileTool::description() const
|
||||
{
|
||||
return "Search for a file by name/path and optionally read its content. "
|
||||
"Returns the best matching file and its content.";
|
||||
}
|
||||
|
||||
QJsonObject FindAndReadFileTool::parametersSchema() const
|
||||
{
|
||||
QJsonObject properties;
|
||||
|
||||
properties["query"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"description", "Filename, partial name, or path to search for (case-insensitive)"}};
|
||||
|
||||
properties["file_pattern"] = QJsonObject{
|
||||
{"type", "string"}, {"description", "File pattern filter (e.g., '*.cpp', '*.h', '*.qml')"}};
|
||||
|
||||
properties["read_content"] = QJsonObject{
|
||||
{"type", "boolean"},
|
||||
{"description", "Read file content in addition to finding path (default: true)"}};
|
||||
|
||||
QJsonObject definition;
|
||||
definition["type"] = "object";
|
||||
definition["properties"] = properties;
|
||||
definition["required"] = QJsonArray{"query"};
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
QFuture<LLMQore::ToolResult> FindAndReadFileTool::executeAsync(const QJsonObject &input)
|
||||
{
|
||||
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
|
||||
QString query = input["query"].toString().trimmed();
|
||||
if (query.isEmpty()) {
|
||||
throw LLMQore::ToolInvalidArgument("Query parameter is required");
|
||||
}
|
||||
|
||||
QString filePattern = input["file_pattern"].toString();
|
||||
bool readContent = input["read_content"].toBool(true);
|
||||
|
||||
LOG_MESSAGE(QString("FindAndReadFileTool: Searching for '%1' (pattern: %2, read: %3)")
|
||||
.arg(query, filePattern.isEmpty() ? "none" : filePattern)
|
||||
.arg(readContent));
|
||||
|
||||
FileSearchUtils::FileMatch bestMatch = FileSearchUtils::findBestMatch(
|
||||
query, filePattern, 10, m_ignoreManager);
|
||||
|
||||
if (bestMatch.absolutePath.isEmpty()) {
|
||||
return LLMQore::ToolResult::text(QString("No file found matching '%1'").arg(query));
|
||||
}
|
||||
|
||||
if (readContent) {
|
||||
bestMatch.content = FileSearchUtils::readFileContent(bestMatch.absolutePath);
|
||||
if (bestMatch.content.isNull()) {
|
||||
bestMatch.error = "Could not read file";
|
||||
}
|
||||
}
|
||||
|
||||
return LLMQore::ToolResult::text(formatResult(bestMatch, readContent));
|
||||
});
|
||||
}
|
||||
|
||||
QString FindAndReadFileTool::formatResult(const FileSearchUtils::FileMatch &match,
|
||||
bool readContent) const
|
||||
{
|
||||
QString result
|
||||
= QString("Found file: %1\nAbsolute path: %2").arg(match.relativePath, match.absolutePath);
|
||||
|
||||
if (readContent) {
|
||||
if (!match.error.isEmpty()) {
|
||||
result += QString("\nError: %1").arg(match.error);
|
||||
} else {
|
||||
result += QString("\n\n=== Content ===\n%1").arg(match.content);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Tools
|
||||
108
tools/FindFileTool.cpp
Normal file
108
tools/FindFileTool.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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 "FindFileTool.hpp"
|
||||
|
||||
#include "FileSearchUtils.hpp"
|
||||
|
||||
#include <LLMQore/ToolExceptions.hpp>
|
||||
|
||||
#include <logger/Logger.hpp>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
FindFileTool::FindFileTool(QObject *parent)
|
||||
: BaseTool(parent)
|
||||
, m_ignoreManager(new Context::IgnoreManager(this))
|
||||
{}
|
||||
|
||||
QString FindFileTool::id() const
|
||||
{
|
||||
return "find_file";
|
||||
}
|
||||
|
||||
QString FindFileTool::displayName() const
|
||||
{
|
||||
return "Finding file";
|
||||
}
|
||||
|
||||
QString FindFileTool::description() const
|
||||
{
|
||||
return "Find a file in the current project(s) by name or partial path. "
|
||||
"Returns the absolute path, project-relative path, and the name of the project "
|
||||
"that contains the match. Does NOT read file content — use `read_file` separately "
|
||||
"when you need the content. Use this when you know part of the filename but not "
|
||||
"the exact location.";
|
||||
}
|
||||
|
||||
QJsonObject FindFileTool::parametersSchema() const
|
||||
{
|
||||
QJsonObject properties;
|
||||
|
||||
properties["query"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"description",
|
||||
"Filename, partial filename, or partial path to look for. Case-insensitive. "
|
||||
"Exact filename matches rank highest, then path matches, then partial name matches."}};
|
||||
|
||||
properties["file_pattern"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"description",
|
||||
"Optional wildcard filter applied to the filename. Examples: '*.cpp', '*.h', '*.qml'. "
|
||||
"Leave empty to match any extension."}};
|
||||
|
||||
QJsonObject definition;
|
||||
definition["type"] = "object";
|
||||
definition["properties"] = properties;
|
||||
definition["required"] = QJsonArray{"query"};
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
QFuture<LLMQore::ToolResult> FindFileTool::executeAsync(const QJsonObject &input)
|
||||
{
|
||||
return QtConcurrent::run([this, input]() -> LLMQore::ToolResult {
|
||||
QString query = input["query"].toString().trimmed();
|
||||
if (query.isEmpty()) {
|
||||
throw LLMQore::ToolInvalidArgument("'query' parameter is required and cannot be empty");
|
||||
}
|
||||
|
||||
QString filePattern = input["file_pattern"].toString();
|
||||
|
||||
LOG_MESSAGE(QString("FindFileTool: Searching for '%1' (pattern: %2)")
|
||||
.arg(query, filePattern.isEmpty() ? "none" : filePattern));
|
||||
|
||||
FileSearchUtils::FileMatch match
|
||||
= FileSearchUtils::findBestMatch(query, filePattern, 10, m_ignoreManager);
|
||||
|
||||
if (match.absolutePath.isEmpty()) {
|
||||
return LLMQore::ToolResult::text(QString("No file found matching '%1'").arg(query));
|
||||
}
|
||||
|
||||
QString result = QString("Found file: %1\nAbsolute path: %2\nProject: %3")
|
||||
.arg(match.relativePath, match.absolutePath, match.projectName);
|
||||
|
||||
return LLMQore::ToolResult::text(result);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Tools
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FileSearchUtils.hpp"
|
||||
|
||||
#include <context/IgnoreManager.hpp>
|
||||
#include <LLMQore/BaseTool.hpp>
|
||||
#include <QFuture>
|
||||
@@ -29,12 +27,12 @@
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
class FindAndReadFileTool : public ::LLMQore::BaseTool
|
||||
class FindFileTool : public ::LLMQore::BaseTool
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FindAndReadFileTool(QObject *parent = nullptr);
|
||||
explicit FindFileTool(QObject *parent = nullptr);
|
||||
|
||||
QString id() const override;
|
||||
QString displayName() const override;
|
||||
@@ -43,8 +41,6 @@ public:
|
||||
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
|
||||
|
||||
private:
|
||||
QString formatResult(const FileSearchUtils::FileMatch &match, bool readContent) const;
|
||||
|
||||
Context::IgnoreManager *m_ignoreManager;
|
||||
};
|
||||
|
||||
@@ -133,9 +133,10 @@ QString GetIssuesListTool::displayName() const
|
||||
|
||||
QString GetIssuesListTool::description() const
|
||||
{
|
||||
return "Get compilation errors, warnings, and diagnostics from Qt Creator's Issues panel. "
|
||||
"Returns issue descriptions with file paths and line numbers. "
|
||||
"Optional severity filter: 'error', 'warning', or 'all' (default).";
|
||||
return "Read diagnostics from Qt Creator's Issues panel, including the latest build output and "
|
||||
"live clang-codemodel warnings/errors for open files. Each issue includes file path, "
|
||||
"line number, severity, and message. Run `build_project` first if you need fresh build "
|
||||
"diagnostics.";
|
||||
}
|
||||
|
||||
QJsonObject GetIssuesListTool::parametersSchema() const
|
||||
|
||||
@@ -50,8 +50,10 @@ QString ListProjectFilesTool::displayName() const
|
||||
|
||||
QString ListProjectFilesTool::description() const
|
||||
{
|
||||
return "Get a list of all source files in the current project with absolute and relative paths. "
|
||||
"Useful for understanding project structure. No parameters required.";
|
||||
return "List every source file tracked by the active Qt Creator project(s), filtered by "
|
||||
".qodeassistignore. Returns absolute and project-relative paths grouped by project. "
|
||||
"Useful for discovering the project layout before running focused searches or reads. "
|
||||
"Takes no parameters.";
|
||||
}
|
||||
|
||||
QJsonObject ListProjectFilesTool::parametersSchema() const
|
||||
|
||||
@@ -56,39 +56,50 @@ QString ProjectSearchTool::displayName() const
|
||||
|
||||
QString ProjectSearchTool::description() const
|
||||
{
|
||||
return "Search project for text content or C++ symbols. "
|
||||
"Text mode: finds text patterns in files. "
|
||||
"Symbol mode: finds C++ definitions (classes, functions, etc).";
|
||||
return "Search across all open Qt Creator project(s) for text occurrences or C++ symbol "
|
||||
"definitions. 'text' mode scans source files line-by-line for literal text or regex. "
|
||||
"'symbol' mode uses Qt Creator's C++ code model and works only for C++ "
|
||||
"(not QML, Python, or plain text). Respects .qodeassistignore. "
|
||||
"Use `find_file` for locating files by name, not this tool.";
|
||||
}
|
||||
|
||||
QJsonObject ProjectSearchTool::parametersSchema() const
|
||||
{
|
||||
QJsonObject properties;
|
||||
|
||||
properties["query"]
|
||||
= QJsonObject{{"type", "string"}, {"description", "Text or symbol name to search for"}};
|
||||
properties["query"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"description", "Text string or symbol name to search for. In text mode with "
|
||||
"use_regex=true, this is a regular expression."}};
|
||||
|
||||
properties["search_type"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"enum", QJsonArray{"text", "symbol"}},
|
||||
{"description", "Search mode: 'text' for content, 'symbol' for C++ definitions"}};
|
||||
{"description", "Search mode: 'text' scans file contents, 'symbol' looks up C++ "
|
||||
"declarations via the code model."}};
|
||||
|
||||
properties["symbol_type"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"enum", QJsonArray{"all", "class", "function", "enum", "variable", "namespace"}},
|
||||
{"description", "Symbol type filter (symbol mode only)"}};
|
||||
{"description", "Filter for symbol mode. Default: 'all'. Ignored in text mode."}};
|
||||
|
||||
properties["case_sensitive"]
|
||||
= QJsonObject{{"type", "boolean"}, {"description", "Case-sensitive search"}};
|
||||
properties["case_sensitive"] = QJsonObject{
|
||||
{"type", "boolean"},
|
||||
{"description", "Case-sensitive matching. Default: false."}};
|
||||
|
||||
properties["use_regex"]
|
||||
= QJsonObject{{"type", "boolean"}, {"description", "Use regex patterns"}};
|
||||
properties["use_regex"] = QJsonObject{
|
||||
{"type", "boolean"},
|
||||
{"description", "Treat the query as a regular expression. Default: false."}};
|
||||
|
||||
properties["whole_words"]
|
||||
= QJsonObject{{"type", "boolean"}, {"description", "Match whole words only (text mode)"}};
|
||||
properties["whole_words"] = QJsonObject{
|
||||
{"type", "boolean"},
|
||||
{"description", "Match whole words only. Text mode only; ignored in symbol mode. "
|
||||
"Default: false."}};
|
||||
|
||||
properties["file_pattern"] = QJsonObject{
|
||||
{"type", "string"}, {"description", "File filter pattern (e.g., '*.cpp', '*.h')"}};
|
||||
{"type", "string"},
|
||||
{"description", "Wildcard to restrict which files are searched in text mode, e.g. "
|
||||
"'*.cpp' or '*.h'. Default: all files."}};
|
||||
|
||||
QJsonObject definition;
|
||||
definition["type"] = "object";
|
||||
|
||||
122
tools/ReadFileTool.cpp
Normal file
122
tools/ReadFileTool.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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 "ReadFileTool.hpp"
|
||||
|
||||
#include "FileSearchUtils.hpp"
|
||||
|
||||
#include <LLMQore/ToolExceptions.hpp>
|
||||
|
||||
#include <context/ProjectUtils.hpp>
|
||||
#include <logger/Logger.hpp>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
ReadFileTool::ReadFileTool(QObject *parent)
|
||||
: BaseTool(parent)
|
||||
{}
|
||||
|
||||
QString ReadFileTool::id() const
|
||||
{
|
||||
return "read_file";
|
||||
}
|
||||
|
||||
QString ReadFileTool::displayName() const
|
||||
{
|
||||
return "Reading file";
|
||||
}
|
||||
|
||||
QString ReadFileTool::description() const
|
||||
{
|
||||
return "Read the text content of a file. Accepts either an absolute path or a path "
|
||||
"relative to the project root. Reading files outside the active project requires "
|
||||
"the 'Allow file access outside project' option in settings. Use `find_file` first "
|
||||
"if you only know a partial filename.";
|
||||
}
|
||||
|
||||
QJsonObject ReadFileTool::parametersSchema() const
|
||||
{
|
||||
QJsonObject properties;
|
||||
|
||||
properties["file_path"] = QJsonObject{
|
||||
{"type", "string"},
|
||||
{"description",
|
||||
"Absolute path (e.g. /path/to/file.cpp) or project-relative path (e.g. src/main.cpp). "
|
||||
"Relative paths are resolved against the root of the active project."}};
|
||||
|
||||
QJsonObject definition;
|
||||
definition["type"] = "object";
|
||||
definition["properties"] = properties;
|
||||
definition["required"] = QJsonArray{"file_path"};
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
QFuture<LLMQore::ToolResult> ReadFileTool::executeAsync(const QJsonObject &input)
|
||||
{
|
||||
return QtConcurrent::run([input]() -> LLMQore::ToolResult {
|
||||
QString rawPath = input["file_path"].toString().trimmed();
|
||||
if (rawPath.isEmpty()) {
|
||||
throw LLMQore::ToolInvalidArgument(
|
||||
"'file_path' parameter is required and cannot be empty");
|
||||
}
|
||||
|
||||
QFileInfo pathInfo(rawPath);
|
||||
QString absolutePath;
|
||||
if (pathInfo.isAbsolute()) {
|
||||
absolutePath = rawPath;
|
||||
} else {
|
||||
QString projectRoot = Context::ProjectUtils::getProjectRoot();
|
||||
if (projectRoot.isEmpty()) {
|
||||
throw LLMQore::ToolRuntimeError(
|
||||
QString("Cannot resolve relative path '%1': no project is open. "
|
||||
"Provide an absolute path or open a project.")
|
||||
.arg(rawPath));
|
||||
}
|
||||
absolutePath = QDir(projectRoot).absoluteFilePath(rawPath);
|
||||
LOG_MESSAGE(QString("ReadFileTool: Resolved relative path '%1' to '%2'")
|
||||
.arg(rawPath, absolutePath));
|
||||
}
|
||||
|
||||
QFileInfo finalInfo(absolutePath);
|
||||
if (!finalInfo.exists() || !finalInfo.isFile()) {
|
||||
throw LLMQore::ToolRuntimeError(QString("File does not exist: %1").arg(absolutePath));
|
||||
}
|
||||
|
||||
QString content = FileSearchUtils::readFileContent(absolutePath);
|
||||
if (content.isNull()) {
|
||||
throw LLMQore::ToolRuntimeError(
|
||||
QString("Cannot read file '%1'. It may be outside the project scope "
|
||||
"(enable 'Allow file access outside project' in settings), unreadable, "
|
||||
"or use an unsupported encoding.")
|
||||
.arg(absolutePath));
|
||||
}
|
||||
|
||||
QString result = QString("File: %1\n\n%2").arg(absolutePath, content);
|
||||
return LLMQore::ToolResult::text(result);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Tools
|
||||
43
tools/ReadFileTool.hpp
Normal file
43
tools/ReadFileTool.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 <LLMQore/BaseTool.hpp>
|
||||
#include <QFuture>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
class ReadFileTool : public ::LLMQore::BaseTool
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ReadFileTool(QObject *parent = nullptr);
|
||||
|
||||
QString id() const override;
|
||||
QString displayName() const override;
|
||||
QString description() const override;
|
||||
QJsonObject parametersSchema() const override;
|
||||
QFuture<LLMQore::ToolResult> executeAsync(const QJsonObject &input) override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Tools
|
||||
@@ -21,29 +21,62 @@
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include <settings/ToolsSettings.hpp>
|
||||
#include <utils/aspects.h>
|
||||
|
||||
#include "BuildProjectTool.hpp"
|
||||
#include "CreateNewFileTool.hpp"
|
||||
#include "EditFileTool.hpp"
|
||||
#include "ExecuteTerminalCommandTool.hpp"
|
||||
#include "FindAndReadFileTool.hpp"
|
||||
#include "FindFileTool.hpp"
|
||||
#include "GetIssuesListTool.hpp"
|
||||
#include "ListProjectFilesTool.hpp"
|
||||
#include "ProjectSearchTool.hpp"
|
||||
#include "ReadFileTool.hpp"
|
||||
#include "TodoTool.hpp"
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename ToolT>
|
||||
void wireTool(::LLMQore::ToolsManager *manager,
|
||||
Utils::BoolAspect &aspect,
|
||||
const QString &toolId)
|
||||
{
|
||||
auto sync = [manager, toolId, &aspect]() {
|
||||
const bool wanted = aspect.volatileValue();
|
||||
const bool present = manager->tool(toolId) != nullptr;
|
||||
if (wanted && !present) {
|
||||
manager->addTool(new ToolT(manager));
|
||||
} else if (!wanted && present) {
|
||||
manager->removeTool(toolId);
|
||||
}
|
||||
};
|
||||
|
||||
sync();
|
||||
|
||||
QObject::connect(&aspect, &Utils::BoolAspect::volatileValueChanged, manager, sync);
|
||||
QObject::connect(&aspect, &Utils::BaseAspect::changed, manager, sync);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerQodeAssistTools(::LLMQore::ToolsManager *manager)
|
||||
{
|
||||
manager->addTool(new ListProjectFilesTool(manager));
|
||||
manager->addTool(new GetIssuesListTool(manager));
|
||||
manager->addTool(new CreateNewFileTool(manager));
|
||||
manager->addTool(new EditFileTool(manager));
|
||||
manager->addTool(new BuildProjectTool(manager));
|
||||
manager->addTool(new ExecuteTerminalCommandTool(manager));
|
||||
manager->addTool(new ProjectSearchTool(manager));
|
||||
manager->addTool(new FindAndReadFileTool(manager));
|
||||
manager->addTool(new TodoTool(manager));
|
||||
auto &s = Settings::toolsSettings();
|
||||
|
||||
wireTool<ListProjectFilesTool>(manager, s.enableListProjectFilesTool, "list_project_files");
|
||||
wireTool<FindFileTool>(manager, s.enableFindFileTool, "find_file");
|
||||
wireTool<ReadFileTool>(manager, s.enableReadFileTool, "read_file");
|
||||
wireTool<ProjectSearchTool>(manager, s.enableProjectSearchTool, "search_project");
|
||||
wireTool<CreateNewFileTool>(manager, s.enableCreateNewFileTool, "create_new_file");
|
||||
wireTool<EditFileTool>(manager, s.enableEditFileTool, "edit_file");
|
||||
wireTool<BuildProjectTool>(manager, s.enableBuildProjectTool, "build_project");
|
||||
wireTool<GetIssuesListTool>(manager, s.enableGetIssuesListTool, "get_issues_list");
|
||||
wireTool<ExecuteTerminalCommandTool>(
|
||||
manager, s.enableTerminalCommandTool, "execute_terminal_command");
|
||||
wireTool<TodoTool>(manager, s.enableTodoTool, "todo_tool");
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Tools
|
||||
|
||||
Reference in New Issue
Block a user