From 080947c0dc9f78f3363523afe7a2370b6263498a Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:32:45 +0200 Subject: [PATCH] feat: Add possibility to overwrite agent config tools enabling --- settings/AgentDetailPane.cpp | 94 +++++++++++++++++++++++++++++++++ settings/AgentDetailPane.hpp | 7 +++ settings/AgentsSettingsPage.cpp | 1 + settings/TagFilterStrip.cpp | 13 +++-- sources/agents/AgentFactory.cpp | 70 ++++++++++++++++++++++++ sources/agents/AgentFactory.hpp | 8 +++ 6 files changed, 188 insertions(+), 5 deletions(-) diff --git a/settings/AgentDetailPane.cpp b/settings/AgentDetailPane.cpp index 476cadf..6f6b6e3 100644 --- a/settings/AgentDetailPane.cpp +++ b/settings/AgentDetailPane.cpp @@ -9,12 +9,15 @@ #include "SettingsTheme.hpp" #include "SettingsUiBuilders.hpp" +#include + #include #include #include #include +#include #include #include #include @@ -29,6 +32,7 @@ #include #include #include +#include #include #include @@ -247,6 +251,43 @@ AgentDetailPane::AgentDetailPane(QWidget *parent) m_effectiveUrl->setAutoFillBackground(true); connection->bodyLayout()->addWidget(m_effectiveUrl); + auto *capabilities = new SectionBox(tr("Capabilities"), this); + m_thinkingValue = new QLabel(this); + + m_toolsCheck = new QCheckBox(tr("Allow tool calls"), this); + m_toolsCheck->setEnabled(false); + connect(m_toolsCheck, &QCheckBox::clicked, this, [this](bool on) { onToggleTools(on); }); + + m_toolsResetBtn = new QPushButton(tr("Reset"), this); + m_toolsResetBtn->setToolTip(tr("Remove the tools override and restore the agent's default")); + m_toolsResetBtn->setVisible(false); + connect(m_toolsResetBtn, &QPushButton::clicked, this, [this] { onResetTools(); }); + + auto *toolsHolder = new QWidget(this); + auto *toolsRow = new QHBoxLayout(toolsHolder); + toolsRow->setContentsMargins(0, 0, 0, 0); + toolsRow->setSpacing(6); + toolsRow->addWidget(m_toolsCheck); + toolsRow->addStretch(1); + toolsRow->addWidget(m_toolsResetBtn); + + auto *capGrid = new QGridLayout; + capGrid->setContentsMargins(0, 0, 0, 0); + capGrid->setHorizontalSpacing(8); + capGrid->setVerticalSpacing(4); + FormBuilder(capGrid) + .row( + tr("Thinking:"), + m_thinkingValue, + tr("Whether this agent requests extended thinking. Defined by the " + "agent template.")) + .row( + tr("Tools:"), + toolsHolder, + tr("Whether the agent may call tools. Toggle to save a per-agent " + "override in settings; Reset restores the agent's default.")); + capabilities->bodyLayout()->addLayout(capGrid); + auto *match = new SectionBox(tr("Match"), this); auto *matchHint = makeHintLabel( tr("When a feature slot has multiple bound agents, the first whose " @@ -286,6 +327,7 @@ AgentDetailPane::AgentDetailPane(QWidget *parent) root->addWidget(m_description); root->addWidget(identity); root->addWidget(connection); + root->addWidget(capabilities); root->addWidget(match); root->addWidget(m_rawToggle, 0, Qt::AlignLeft); root->addWidget(m_rawToml); @@ -414,6 +456,38 @@ void AgentDetailPane::onResetProvider() setAgent(*cfg); } +void AgentDetailPane::onToggleTools(bool enabled) +{ + if (!m_agentFactory || !m_current) + return; + + const QString name = m_current->name; + QString err; + if (!m_agentFactory->setAgentToolsOverride(name, enabled, &err)) { + QMessageBox::warning(this, tr("Set tools"), err); + return; + } + if (const AgentConfig *cfg = m_agentFactory->configByName(name)) + setAgent(*cfg); +} + +void AgentDetailPane::onResetTools() +{ + if (!m_agentFactory || !m_current) + return; + if (!m_agentFactory->agentToolsOverride(m_current->name).has_value()) + return; + + const QString name = m_current->name; + QString err; + if (!m_agentFactory->setAgentToolsOverride(name, std::nullopt, &err)) { + QMessageBox::warning(this, tr("Reset tools"), err); + return; + } + if (const AgentConfig *cfg = m_agentFactory->configByName(name)) + setAgent(*cfg); +} + void AgentDetailPane::setAgent(const AgentConfig &cfg) { m_currentStorage = cfg; @@ -481,6 +555,19 @@ void AgentDetailPane::setAgent(const AgentConfig &cfg) hasModelOverride ? tr("Overridden in settings. Reset to use the agent's default model.") : QString()); + m_thinkingValue->setText(cfg.enableThinking ? tr("Enabled") : tr("Disabled")); + { + QSignalBlocker block(m_toolsCheck); + m_toolsCheck->setChecked(cfg.enableTools); + } + m_toolsCheck->setEnabled(m_agentFactory != nullptr); + const bool hasToolsOverride = m_agentFactory + && m_agentFactory->agentToolsOverride(cfg.name).has_value(); + m_toolsResetBtn->setVisible(hasToolsOverride); + m_toolsCheck->setToolTip( + hasToolsOverride ? tr("Overridden in settings. Reset to use the agent's default.") + : QString()); + const QString eff = resolvedUrl + cfg.endpoint; m_effectiveUrl->setText( eff.isEmpty() ? tr("# effective request line\n(unknown — provider instance not found)") @@ -542,6 +629,13 @@ void AgentDetailPane::clear() m_modelChangeBtn->setEnabled(false); m_modelResetBtn->setVisible(false); m_effectiveUrl->clear(); + m_thinkingValue->clear(); + { + QSignalBlocker block(m_toolsCheck); + m_toolsCheck->setChecked(false); + } + m_toolsCheck->setEnabled(false); + m_toolsResetBtn->setVisible(false); m_filePatternsValue->clear(); m_rawToml->clear(); m_openBtn->setEnabled(false); diff --git a/settings/AgentDetailPane.hpp b/settings/AgentDetailPane.hpp index 7168e46..42322b3 100644 --- a/settings/AgentDetailPane.hpp +++ b/settings/AgentDetailPane.hpp @@ -10,6 +10,7 @@ #include +class QCheckBox; class QComboBox; class QLabel; class QLineEdit; @@ -56,6 +57,8 @@ private: void onResetModel(); void onChangeProvider(int index); void onResetProvider(); + void onToggleTools(bool enabled); + void onResetTools(); bool m_inApplyPalette = false; bool m_providerComboPopulated = false; @@ -88,6 +91,10 @@ private: QPushButton *m_modelResetBtn = nullptr; QLabel *m_effectiveUrl = nullptr; + QLabel *m_thinkingValue = nullptr; + QCheckBox *m_toolsCheck = nullptr; + QPushButton *m_toolsResetBtn = nullptr; + QLineEdit *m_filePatternsValue = nullptr; QToolButton *m_rawToggle = nullptr; diff --git a/settings/AgentsSettingsPage.cpp b/settings/AgentsSettingsPage.cpp index a60a2f4..8c05e78 100644 --- a/settings/AgentsSettingsPage.cpp +++ b/settings/AgentsSettingsPage.cpp @@ -243,6 +243,7 @@ private: } m_agentFactory->clearAgentModelOverride(name); m_agentFactory->clearAgentProviderOverride(name); + m_agentFactory->clearAgentToolsOverride(name); reloadFromDisk(); } diff --git a/settings/TagFilterStrip.cpp b/settings/TagFilterStrip.cpp index 446c05a..1d90f7f 100644 --- a/settings/TagFilterStrip.cpp +++ b/settings/TagFilterStrip.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -164,6 +165,7 @@ void TagFilterStrip::rebuild() return a.first.localeAwareCompare(b.first) < 0; }); + constexpr int kMaxColumns = 3; auto *gridHost = new QWidget(this); auto *grid = new QGridLayout(gridHost); grid->setContentsMargins(0, 0, 0, 0); @@ -176,12 +178,11 @@ void TagFilterStrip::rebuild() connect(chip, &TagChip::clicked, this, &TagFilterStrip::toggleTag); grid->addWidget(chip, gridRow, col, Qt::AlignLeft); m_chipByTag.insert(tag, chip); - if (++col >= 4) { + if (++col >= kMaxColumns) { col = 0; ++gridRow; } } - grid->setColumnStretch(4, 1); constexpr int kMaxVisibleRows = 4; constexpr int kRowSpacing = 5; @@ -194,10 +195,12 @@ void TagFilterStrip::rebuild() scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll->setWidget(gridHost); scroll->setMaximumHeight( - kMaxVisibleRows * chipHeight + (kMaxVisibleRows - 1) * kRowSpacing + 4); - m_layout->addWidget(scroll); + kMaxVisibleRows * chipHeight + (kMaxVisibleRows - 1) * kRowSpacing + chipHeight / 2 + 4); + const int scrollBarWidth = scroll->verticalScrollBar()->sizeHint().width(); + scroll->setFixedWidth(gridHost->sizeHint().width() + scrollBarWidth + 2); + m_layout->addWidget(scroll, 0, Qt::AlignLeft); } else { - m_layout->addWidget(gridHost); + m_layout->addWidget(gridHost, 0, Qt::AlignLeft); } } diff --git a/sources/agents/AgentFactory.cpp b/sources/agents/AgentFactory.cpp index eca95e3..dd8eaf5 100644 --- a/sources/agents/AgentFactory.cpp +++ b/sources/agents/AgentFactory.cpp @@ -89,6 +89,37 @@ void writeProviderOverride(const QString &name, const QString &providerInstance) s->setValue(providerOverrideKey(name), providerInstance); s->sync(); } + +constexpr auto kToolsOverrideGroup = "QodeAssist/AgentToolsOverrides"; + +Utils::Key toolsOverrideKey(const QString &name) +{ + return Utils::Key( + QStringLiteral("%1/%2").arg(QLatin1StringView(kToolsOverrideGroup), name).toUtf8()); +} + +std::optional readToolsOverride(const QString &name) +{ + auto *s = Core::ICore::settings(); + if (!s) + return std::nullopt; + const Utils::Key key = toolsOverrideKey(name); + if (!s->contains(key)) + return std::nullopt; + return s->value(key).toBool(); +} + +void writeToolsOverride(const QString &name, std::optional enabled) +{ + auto *s = Core::ICore::settings(); + if (!s) + return; + if (!enabled.has_value()) + s->remove(toolsOverrideKey(name)); + else + s->setValue(toolsOverrideKey(name), *enabled); + s->sync(); +} } // namespace namespace QodeAssist { @@ -146,6 +177,11 @@ void AgentFactory::reload() const QString overrideProvider = readProviderOverride(cfg.name); if (!overrideProvider.isEmpty()) cfg.providerInstance = overrideProvider; + + m_baseToolsByName.insert(cfg.name, cfg.enableTools); + const std::optional overrideTools = readToolsOverride(cfg.name); + if (overrideTools.has_value()) + cfg.enableTools = *overrideTools; } emit agentsChanged(); } @@ -289,6 +325,7 @@ void AgentFactory::clear() m_indexByName.clear(); m_baseModelByName.clear(); m_baseProviderByName.clear(); + m_baseToolsByName.clear(); } Providers::ProviderInstanceFactory *AgentFactory::instanceFactory() const noexcept @@ -361,4 +398,37 @@ void AgentFactory::clearAgentProviderOverride(const QString &name) writeProviderOverride(name, QString()); } +bool AgentFactory::setAgentToolsOverride( + const QString &name, std::optional enabled, QString *error) +{ + Q_ASSERT(thread() == QThread::currentThread()); + + const auto it = m_indexByName.constFind(name); + if (it == m_indexByName.constEnd()) { + if (error) + *error = QStringLiteral("Agent '%1' is not registered").arg(name); + return false; + } + AgentConfig &cfg = m_configs[it.value()]; + const bool effective = enabled.has_value() ? *enabled + : m_baseToolsByName.value(name, cfg.enableTools); + if (cfg.enableTools == effective && readToolsOverride(name) == enabled) + return true; + + writeToolsOverride(name, enabled); + cfg.enableTools = effective; + emit agentToolsChanged(name); + return true; +} + +std::optional AgentFactory::agentToolsOverride(const QString &name) const +{ + return readToolsOverride(name); +} + +void AgentFactory::clearAgentToolsOverride(const QString &name) +{ + writeToolsOverride(name, std::nullopt); +} + } // namespace QodeAssist diff --git a/sources/agents/AgentFactory.hpp b/sources/agents/AgentFactory.hpp index 601a475..dd6b4af 100644 --- a/sources/agents/AgentFactory.hpp +++ b/sources/agents/AgentFactory.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -60,18 +61,25 @@ public: [[nodiscard]] QString agentProviderOverride(const QString &name) const; void clearAgentProviderOverride(const QString &name); + bool setAgentToolsOverride( + const QString &name, std::optional enabled, QString *error = nullptr); + [[nodiscard]] std::optional agentToolsOverride(const QString &name) const; + void clearAgentToolsOverride(const QString &name); + [[nodiscard]] Providers::ProviderInstanceFactory *instanceFactory() const noexcept; signals: void agentsChanged(); void agentModelChanged(const QString &name); void agentProviderChanged(const QString &name); + void agentToolsChanged(const QString &name); private: std::vector m_configs; QHash m_indexByName; QHash m_baseModelByName; QHash m_baseProviderByName; + QHash m_baseToolsByName; QPointer m_instanceFactory; QPointer m_secrets; };