mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 02:49:12 -04:00
feat: Add too for reading original message history after compressing
This commit is contained in:
@@ -146,6 +146,7 @@ add_qtc_plugin(QodeAssist
|
|||||||
tools/ReadFileTool.hpp tools/ReadFileTool.cpp
|
tools/ReadFileTool.hpp tools/ReadFileTool.cpp
|
||||||
tools/FileSearchUtils.hpp tools/FileSearchUtils.cpp
|
tools/FileSearchUtils.hpp tools/FileSearchUtils.cpp
|
||||||
tools/TodoTool.hpp tools/TodoTool.cpp
|
tools/TodoTool.hpp tools/TodoTool.cpp
|
||||||
|
tools/ReadOriginalHistoryTool.hpp tools/ReadOriginalHistoryTool.cpp
|
||||||
mcp/McpServerManager.hpp mcp/McpServerManager.cpp
|
mcp/McpServerManager.hpp mcp/McpServerManager.cpp
|
||||||
mcp/McpServerConnection.hpp mcp/McpServerConnection.cpp
|
mcp/McpServerConnection.hpp mcp/McpServerConnection.cpp
|
||||||
mcp/McpClientsManager.hpp mcp/McpClientsManager.cpp
|
mcp/McpClientsManager.hpp mcp/McpClientsManager.cpp
|
||||||
|
|||||||
@@ -228,6 +228,8 @@ bool ChatCompressor::createCompressedChatFile(
|
|||||||
summaryMessage["images"] = QJsonArray();
|
summaryMessage["images"] = QJsonArray();
|
||||||
|
|
||||||
root["messages"] = QJsonArray{summaryMessage};
|
root["messages"] = QJsonArray{summaryMessage};
|
||||||
|
root["compressedFrom"] = sourcePath;
|
||||||
|
root["compressedAt"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||||
|
|
||||||
if (QFile::exists(destPath))
|
if (QFile::exists(destPath))
|
||||||
QFile::remove(destPath);
|
QFile::remove(destPath);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include <LLMQore/ToolsManager.hpp>
|
#include <LLMQore/ToolsManager.hpp>
|
||||||
|
|
||||||
|
#include "tools/ReadOriginalHistoryTool.hpp"
|
||||||
#include "tools/TodoTool.hpp"
|
#include "tools/TodoTool.hpp"
|
||||||
|
|
||||||
#include "ChatAssistantSettings.hpp"
|
#include "ChatAssistantSettings.hpp"
|
||||||
@@ -305,6 +306,10 @@ void ClientInterface::sendMessage(
|
|||||||
provider->toolsManager()->tool("todo_tool"))) {
|
provider->toolsManager()->tool("todo_tool"))) {
|
||||||
todoTool->setCurrentSessionId(m_chatFilePath);
|
todoTool->setCurrentSessionId(m_chatFilePath);
|
||||||
}
|
}
|
||||||
|
if (auto *historyTool = qobject_cast<QodeAssist::Tools::ReadOriginalHistoryTool *>(
|
||||||
|
provider->toolsManager()->tool("read_original_history"))) {
|
||||||
|
historyTool->setCurrentSessionId(m_chatFilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ const char CA_ENABLE_EDIT_FILE_TOOL[] = "QodeAssist.caEnableEditFileToolV2";
|
|||||||
const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectToolV2";
|
const char CA_ENABLE_BUILD_PROJECT_TOOL[] = "QodeAssist.caEnableBuildProjectToolV2";
|
||||||
const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandToolV2";
|
const char CA_ENABLE_TERMINAL_COMMAND_TOOL[] = "QodeAssist.caEnableTerminalCommandToolV2";
|
||||||
const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoToolV2";
|
const char CA_ENABLE_TODO_TOOL[] = "QodeAssist.caEnableTodoToolV2";
|
||||||
|
const char CA_ENABLE_READ_ORIGINAL_HISTORY_TOOL[]
|
||||||
|
= "QodeAssist.caEnableReadOriginalHistoryTool";
|
||||||
const char CA_ALLOWED_TERMINAL_COMMANDS[] = "QodeAssist.caAllowedTerminalCommands";
|
const char CA_ALLOWED_TERMINAL_COMMANDS[] = "QodeAssist.caAllowedTerminalCommands";
|
||||||
const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux";
|
const char CA_ALLOWED_TERMINAL_COMMANDS_LINUX[] = "QodeAssist.caAllowedTerminalCommandsLinux";
|
||||||
const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS";
|
const char CA_ALLOWED_TERMINAL_COMMANDS_MACOS[] = "QodeAssist.caAllowedTerminalCommandsMacOS";
|
||||||
|
|||||||
@@ -111,6 +111,14 @@ ToolsSettings::ToolsSettings()
|
|||||||
Tr::tr("Lets the AI maintain a session-scoped todo list for multi-step workflows."));
|
Tr::tr("Lets the AI maintain a session-scoped todo list for multi-step workflows."));
|
||||||
enableTodoTool.setDefaultValue(true);
|
enableTodoTool.setDefaultValue(true);
|
||||||
|
|
||||||
|
enableReadOriginalHistoryTool.setSettingsKey(Constants::CA_ENABLE_READ_ORIGINAL_HISTORY_TOOL);
|
||||||
|
enableReadOriginalHistoryTool.setLabelText(Tr::tr("Read Original History (Pre-Compression)"));
|
||||||
|
enableReadOriginalHistoryTool.setToolTip(
|
||||||
|
Tr::tr("Lets the AI read the original, full chat history from before the conversation "
|
||||||
|
"was compressed into a summary. Useful when a detail is missing from the "
|
||||||
|
"summary currently in context. Has no effect if the chat was never compressed."));
|
||||||
|
enableReadOriginalHistoryTool.setDefaultValue(true);
|
||||||
|
|
||||||
allowedTerminalCommandsLinux.setSettingsKey(Constants::CA_ALLOWED_TERMINAL_COMMANDS_LINUX);
|
allowedTerminalCommandsLinux.setSettingsKey(Constants::CA_ALLOWED_TERMINAL_COMMANDS_LINUX);
|
||||||
allowedTerminalCommandsLinux.setLabelText(Tr::tr("Allowed Commands (Linux)"));
|
allowedTerminalCommandsLinux.setLabelText(Tr::tr("Allowed Commands (Linux)"));
|
||||||
allowedTerminalCommandsLinux.setToolTip(
|
allowedTerminalCommandsLinux.setToolTip(
|
||||||
@@ -177,7 +185,8 @@ ToolsSettings::ToolsSettings()
|
|||||||
enableBuildProjectTool,
|
enableBuildProjectTool,
|
||||||
enableGetIssuesListTool,
|
enableGetIssuesListTool,
|
||||||
enableTerminalCommandTool,
|
enableTerminalCommandTool,
|
||||||
enableTodoTool}},
|
enableTodoTool,
|
||||||
|
enableReadOriginalHistoryTool}},
|
||||||
Space{8},
|
Space{8},
|
||||||
Group{
|
Group{
|
||||||
title(Tr::tr("Tool Settings")),
|
title(Tr::tr("Tool Settings")),
|
||||||
@@ -227,6 +236,7 @@ void ToolsSettings::resetSettingsToDefaults()
|
|||||||
resetAspect(enableGetIssuesListTool);
|
resetAspect(enableGetIssuesListTool);
|
||||||
resetAspect(enableTerminalCommandTool);
|
resetAspect(enableTerminalCommandTool);
|
||||||
resetAspect(enableTodoTool);
|
resetAspect(enableTodoTool);
|
||||||
|
resetAspect(enableReadOriginalHistoryTool);
|
||||||
resetAspect(allowedTerminalCommandsLinux);
|
resetAspect(allowedTerminalCommandsLinux);
|
||||||
resetAspect(allowedTerminalCommandsMacOS);
|
resetAspect(allowedTerminalCommandsMacOS);
|
||||||
resetAspect(allowedTerminalCommandsWindows);
|
resetAspect(allowedTerminalCommandsWindows);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public:
|
|||||||
Utils::BoolAspect enableGetIssuesListTool{this};
|
Utils::BoolAspect enableGetIssuesListTool{this};
|
||||||
Utils::BoolAspect enableTerminalCommandTool{this};
|
Utils::BoolAspect enableTerminalCommandTool{this};
|
||||||
Utils::BoolAspect enableTodoTool{this};
|
Utils::BoolAspect enableTodoTool{this};
|
||||||
|
Utils::BoolAspect enableReadOriginalHistoryTool{this};
|
||||||
|
|
||||||
Utils::StringAspect allowedTerminalCommandsLinux{this};
|
Utils::StringAspect allowedTerminalCommandsLinux{this};
|
||||||
Utils::StringAspect allowedTerminalCommandsMacOS{this};
|
Utils::StringAspect allowedTerminalCommandsMacOS{this};
|
||||||
|
|||||||
196
tools/ReadOriginalHistoryTool.cpp
Normal file
196
tools/ReadOriginalHistoryTool.cpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// Copyright (C) 2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "ReadOriginalHistoryTool.hpp"
|
||||||
|
|
||||||
|
#include <LLMQore/ToolExceptions.hpp>
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QString roleName(int role)
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case 0:
|
||||||
|
return QStringLiteral("system");
|
||||||
|
case 1:
|
||||||
|
return QStringLiteral("user");
|
||||||
|
case 2:
|
||||||
|
return QStringLiteral("assistant");
|
||||||
|
case 3:
|
||||||
|
return QStringLiteral("tool");
|
||||||
|
case 4:
|
||||||
|
return QStringLiteral("file_edit");
|
||||||
|
case 5:
|
||||||
|
return QStringLiteral("thinking");
|
||||||
|
default:
|
||||||
|
return QStringLiteral("unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject readJsonObject(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||||
|
return doc.isObject() ? doc.object() : QJsonObject{};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString resolveRootHistoryPath(const QString &sessionPath)
|
||||||
|
{
|
||||||
|
QString current = sessionPath;
|
||||||
|
QString rootPath;
|
||||||
|
|
||||||
|
for (int depth = 0; depth < 32; ++depth) {
|
||||||
|
const QJsonObject obj = readJsonObject(current);
|
||||||
|
const QString parent = obj.value("compressedFrom").toString();
|
||||||
|
if (parent.isEmpty() || parent == current)
|
||||||
|
break;
|
||||||
|
if (!QFile::exists(parent))
|
||||||
|
break;
|
||||||
|
rootPath = parent;
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ReadOriginalHistoryTool::ReadOriginalHistoryTool(QObject *parent)
|
||||||
|
: BaseTool(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString ReadOriginalHistoryTool::id() const
|
||||||
|
{
|
||||||
|
return "read_original_history";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ReadOriginalHistoryTool::displayName() const
|
||||||
|
{
|
||||||
|
return "Reading pre-compression history";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ReadOriginalHistoryTool::description() const
|
||||||
|
{
|
||||||
|
return "Read the original, full chat history from before this conversation was "
|
||||||
|
"compressed into a summary. Use this only when the summary in context is "
|
||||||
|
"missing a detail you need (an exact code snippet, file path, decision, or "
|
||||||
|
"wording). The result can be large, so prefer the 'query' parameter to search "
|
||||||
|
"and 'offset'/'limit' to page through messages. Returns nothing useful if the "
|
||||||
|
"conversation was never compressed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ReadOriginalHistoryTool::parametersSchema() const
|
||||||
|
{
|
||||||
|
QJsonObject properties;
|
||||||
|
|
||||||
|
properties["query"] = QJsonObject{
|
||||||
|
{"type", "string"},
|
||||||
|
{"description",
|
||||||
|
"Optional case-insensitive substring. When set, only messages whose content "
|
||||||
|
"contains it are returned."}};
|
||||||
|
|
||||||
|
properties["role"] = QJsonObject{
|
||||||
|
{"type", "string"},
|
||||||
|
{"description",
|
||||||
|
"Optional role filter: 'user', 'assistant', 'system' or 'tool'."}};
|
||||||
|
|
||||||
|
properties["offset"] = QJsonObject{
|
||||||
|
{"type", "integer"},
|
||||||
|
{"description", "Index of the first matching message to return (default 0)."}};
|
||||||
|
|
||||||
|
properties["limit"] = QJsonObject{
|
||||||
|
{"type", "integer"},
|
||||||
|
{"description", "Maximum number of messages to return (default 20)."}};
|
||||||
|
|
||||||
|
QJsonObject definition;
|
||||||
|
definition["type"] = "object";
|
||||||
|
definition["properties"] = properties;
|
||||||
|
definition["required"] = QJsonArray{};
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFuture<LLMQore::ToolResult> ReadOriginalHistoryTool::executeAsync(const QJsonObject &input)
|
||||||
|
{
|
||||||
|
QString sessionPath;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
sessionPath = m_currentSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QtConcurrent::run([input, sessionPath]() -> LLMQore::ToolResult {
|
||||||
|
if (sessionPath.isEmpty()) {
|
||||||
|
throw LLMQore::ToolRuntimeError(
|
||||||
|
"No active chat session, cannot locate pre-compression history.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString rootPath = resolveRootHistoryPath(sessionPath);
|
||||||
|
if (rootPath.isEmpty()) {
|
||||||
|
return LLMQore::ToolResult::text(
|
||||||
|
"This conversation was never compressed; there is no separate "
|
||||||
|
"pre-compression history. The messages already in context are the full "
|
||||||
|
"history.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject root = readJsonObject(rootPath);
|
||||||
|
const QJsonArray messages = root.value("messages").toArray();
|
||||||
|
|
||||||
|
const QString query = input.value("query").toString().trimmed();
|
||||||
|
const QString roleFilter = input.value("role").toString().trimmed().toLower();
|
||||||
|
const int offset = qMax(0, input.value("offset").toInt(0));
|
||||||
|
const int limit = qBound(1, input.value("limit").toInt(20), 200);
|
||||||
|
|
||||||
|
QStringList matched;
|
||||||
|
int matchCount = 0;
|
||||||
|
for (int i = 0; i < messages.size(); ++i) {
|
||||||
|
const QJsonObject msg = messages.at(i).toObject();
|
||||||
|
const QString role = roleName(msg.value("role").toInt());
|
||||||
|
const QString content = msg.value("content").toString();
|
||||||
|
|
||||||
|
if (!roleFilter.isEmpty() && role != roleFilter)
|
||||||
|
continue;
|
||||||
|
if (!query.isEmpty() && !content.contains(query, Qt::CaseInsensitive))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
++matchCount;
|
||||||
|
if (matchCount <= offset || matched.size() >= limit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
matched.append(QString("[#%1 %2]\n%3").arg(i).arg(role, content));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int shown = matched.size();
|
||||||
|
QString header = QString("Pre-compression history (%1): %2 matching message(s)")
|
||||||
|
.arg(rootPath)
|
||||||
|
.arg(matchCount);
|
||||||
|
if (shown < matchCount || offset > 0) {
|
||||||
|
header += QString(", showing %1-%2")
|
||||||
|
.arg(offset + 1)
|
||||||
|
.arg(offset + shown);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shown == 0)
|
||||||
|
return LLMQore::ToolResult::text(header + "\n\nNo messages to display.");
|
||||||
|
|
||||||
|
return LLMQore::ToolResult::text(header + "\n\n" + matched.join("\n\n---\n\n"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadOriginalHistoryTool::setCurrentSessionId(const QString &sessionId)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
m_currentSessionId = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Tools
|
||||||
34
tools/ReadOriginalHistoryTool.hpp
Normal file
34
tools/ReadOriginalHistoryTool.hpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (C) 2026 Petr Mironychev
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LLMQore/BaseTool.hpp>
|
||||||
|
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace QodeAssist::Tools {
|
||||||
|
|
||||||
|
class ReadOriginalHistoryTool : public ::LLMQore::BaseTool
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ReadOriginalHistoryTool(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 = QJsonObject()) override;
|
||||||
|
|
||||||
|
void setCurrentSessionId(const QString &sessionId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable QMutex m_mutex;
|
||||||
|
QString m_currentSessionId;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QodeAssist::Tools
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "ListProjectFilesTool.hpp"
|
#include "ListProjectFilesTool.hpp"
|
||||||
#include "ProjectSearchTool.hpp"
|
#include "ProjectSearchTool.hpp"
|
||||||
#include "ReadFileTool.hpp"
|
#include "ReadFileTool.hpp"
|
||||||
|
#include "ReadOriginalHistoryTool.hpp"
|
||||||
#include "TodoTool.hpp"
|
#include "TodoTool.hpp"
|
||||||
|
|
||||||
namespace QodeAssist::Tools {
|
namespace QodeAssist::Tools {
|
||||||
@@ -61,6 +62,8 @@ void registerQodeAssistTools(::LLMQore::ToolsManager *manager)
|
|||||||
wireTool<ExecuteTerminalCommandTool>(
|
wireTool<ExecuteTerminalCommandTool>(
|
||||||
manager, s.enableTerminalCommandTool, "execute_terminal_command");
|
manager, s.enableTerminalCommandTool, "execute_terminal_command");
|
||||||
wireTool<TodoTool>(manager, s.enableTodoTool, "todo_tool");
|
wireTool<TodoTool>(manager, s.enableTodoTool, "todo_tool");
|
||||||
|
wireTool<ReadOriginalHistoryTool>(
|
||||||
|
manager, s.enableReadOriginalHistoryTool, "read_original_history");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QodeAssist::Tools
|
} // namespace QodeAssist::Tools
|
||||||
|
|||||||
Reference in New Issue
Block a user