feat: Add possibility to overwrite agent config tools enabling

This commit is contained in:
Petr Mironychev
2026-06-29 18:32:45 +02:00
parent 9cc57c602b
commit 080947c0dc
6 changed files with 188 additions and 5 deletions

View File

@@ -9,12 +9,15 @@
#include "SettingsTheme.hpp" #include "SettingsTheme.hpp"
#include "SettingsUiBuilders.hpp" #include "SettingsUiBuilders.hpp"
#include <optional>
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
#include <AgentFactory.hpp> #include <AgentFactory.hpp>
#include <ProviderInstance.hpp> #include <ProviderInstance.hpp>
#include <ProviderInstanceFactory.hpp> #include <ProviderInstanceFactory.hpp>
#include <QCheckBox>
#include <QColor> #include <QColor>
#include <QComboBox> #include <QComboBox>
#include <QEvent> #include <QEvent>
@@ -29,6 +32,7 @@
#include <QPlainTextEdit> #include <QPlainTextEdit>
#include <QPushButton> #include <QPushButton>
#include <QScopedValueRollback> #include <QScopedValueRollback>
#include <QSignalBlocker>
#include <QToolButton> #include <QToolButton>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -247,6 +251,43 @@ AgentDetailPane::AgentDetailPane(QWidget *parent)
m_effectiveUrl->setAutoFillBackground(true); m_effectiveUrl->setAutoFillBackground(true);
connection->bodyLayout()->addWidget(m_effectiveUrl); 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 *match = new SectionBox(tr("Match"), this);
auto *matchHint = makeHintLabel( auto *matchHint = makeHintLabel(
tr("When a feature slot has multiple bound agents, the first whose " 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(m_description);
root->addWidget(identity); root->addWidget(identity);
root->addWidget(connection); root->addWidget(connection);
root->addWidget(capabilities);
root->addWidget(match); root->addWidget(match);
root->addWidget(m_rawToggle, 0, Qt::AlignLeft); root->addWidget(m_rawToggle, 0, Qt::AlignLeft);
root->addWidget(m_rawToml); root->addWidget(m_rawToml);
@@ -414,6 +456,38 @@ void AgentDetailPane::onResetProvider()
setAgent(*cfg); 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) void AgentDetailPane::setAgent(const AgentConfig &cfg)
{ {
m_currentStorage = 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.") hasModelOverride ? tr("Overridden in settings. Reset to use the agent's default model.")
: QString()); : 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; const QString eff = resolvedUrl + cfg.endpoint;
m_effectiveUrl->setText( m_effectiveUrl->setText(
eff.isEmpty() ? tr("# effective request line\n(unknown — provider instance not found)") eff.isEmpty() ? tr("# effective request line\n(unknown — provider instance not found)")
@@ -542,6 +629,13 @@ void AgentDetailPane::clear()
m_modelChangeBtn->setEnabled(false); m_modelChangeBtn->setEnabled(false);
m_modelResetBtn->setVisible(false); m_modelResetBtn->setVisible(false);
m_effectiveUrl->clear(); 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_filePatternsValue->clear();
m_rawToml->clear(); m_rawToml->clear();
m_openBtn->setEnabled(false); m_openBtn->setEnabled(false);

View File

@@ -10,6 +10,7 @@
#include <AgentConfig.hpp> #include <AgentConfig.hpp>
class QCheckBox;
class QComboBox; class QComboBox;
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
@@ -56,6 +57,8 @@ private:
void onResetModel(); void onResetModel();
void onChangeProvider(int index); void onChangeProvider(int index);
void onResetProvider(); void onResetProvider();
void onToggleTools(bool enabled);
void onResetTools();
bool m_inApplyPalette = false; bool m_inApplyPalette = false;
bool m_providerComboPopulated = false; bool m_providerComboPopulated = false;
@@ -88,6 +91,10 @@ private:
QPushButton *m_modelResetBtn = nullptr; QPushButton *m_modelResetBtn = nullptr;
QLabel *m_effectiveUrl = nullptr; QLabel *m_effectiveUrl = nullptr;
QLabel *m_thinkingValue = nullptr;
QCheckBox *m_toolsCheck = nullptr;
QPushButton *m_toolsResetBtn = nullptr;
QLineEdit *m_filePatternsValue = nullptr; QLineEdit *m_filePatternsValue = nullptr;
QToolButton *m_rawToggle = nullptr; QToolButton *m_rawToggle = nullptr;

View File

@@ -243,6 +243,7 @@ private:
} }
m_agentFactory->clearAgentModelOverride(name); m_agentFactory->clearAgentModelOverride(name);
m_agentFactory->clearAgentProviderOverride(name); m_agentFactory->clearAgentProviderOverride(name);
m_agentFactory->clearAgentToolsOverride(name);
reloadFromDisk(); reloadFromDisk();
} }

View File

@@ -18,6 +18,7 @@
#include <QPalette> #include <QPalette>
#include <QScopedValueRollback> #include <QScopedValueRollback>
#include <QScrollArea> #include <QScrollArea>
#include <QScrollBar>
#include <QStringList> #include <QStringList>
#include <QTimer> #include <QTimer>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -164,6 +165,7 @@ void TagFilterStrip::rebuild()
return a.first.localeAwareCompare(b.first) < 0; return a.first.localeAwareCompare(b.first) < 0;
}); });
constexpr int kMaxColumns = 3;
auto *gridHost = new QWidget(this); auto *gridHost = new QWidget(this);
auto *grid = new QGridLayout(gridHost); auto *grid = new QGridLayout(gridHost);
grid->setContentsMargins(0, 0, 0, 0); grid->setContentsMargins(0, 0, 0, 0);
@@ -176,12 +178,11 @@ void TagFilterStrip::rebuild()
connect(chip, &TagChip::clicked, this, &TagFilterStrip::toggleTag); connect(chip, &TagChip::clicked, this, &TagFilterStrip::toggleTag);
grid->addWidget(chip, gridRow, col, Qt::AlignLeft); grid->addWidget(chip, gridRow, col, Qt::AlignLeft);
m_chipByTag.insert(tag, chip); m_chipByTag.insert(tag, chip);
if (++col >= 4) { if (++col >= kMaxColumns) {
col = 0; col = 0;
++gridRow; ++gridRow;
} }
} }
grid->setColumnStretch(4, 1);
constexpr int kMaxVisibleRows = 4; constexpr int kMaxVisibleRows = 4;
constexpr int kRowSpacing = 5; constexpr int kRowSpacing = 5;
@@ -194,10 +195,12 @@ void TagFilterStrip::rebuild()
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll->setWidget(gridHost); scroll->setWidget(gridHost);
scroll->setMaximumHeight( scroll->setMaximumHeight(
kMaxVisibleRows * chipHeight + (kMaxVisibleRows - 1) * kRowSpacing + 4); kMaxVisibleRows * chipHeight + (kMaxVisibleRows - 1) * kRowSpacing + chipHeight / 2 + 4);
m_layout->addWidget(scroll); const int scrollBarWidth = scroll->verticalScrollBar()->sizeHint().width();
scroll->setFixedWidth(gridHost->sizeHint().width() + scrollBarWidth + 2);
m_layout->addWidget(scroll, 0, Qt::AlignLeft);
} else { } else {
m_layout->addWidget(gridHost); m_layout->addWidget(gridHost, 0, Qt::AlignLeft);
} }
} }

View File

@@ -89,6 +89,37 @@ void writeProviderOverride(const QString &name, const QString &providerInstance)
s->setValue(providerOverrideKey(name), providerInstance); s->setValue(providerOverrideKey(name), providerInstance);
s->sync(); 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<bool> 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<bool> 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
namespace QodeAssist { namespace QodeAssist {
@@ -146,6 +177,11 @@ void AgentFactory::reload()
const QString overrideProvider = readProviderOverride(cfg.name); const QString overrideProvider = readProviderOverride(cfg.name);
if (!overrideProvider.isEmpty()) if (!overrideProvider.isEmpty())
cfg.providerInstance = overrideProvider; cfg.providerInstance = overrideProvider;
m_baseToolsByName.insert(cfg.name, cfg.enableTools);
const std::optional<bool> overrideTools = readToolsOverride(cfg.name);
if (overrideTools.has_value())
cfg.enableTools = *overrideTools;
} }
emit agentsChanged(); emit agentsChanged();
} }
@@ -289,6 +325,7 @@ void AgentFactory::clear()
m_indexByName.clear(); m_indexByName.clear();
m_baseModelByName.clear(); m_baseModelByName.clear();
m_baseProviderByName.clear(); m_baseProviderByName.clear();
m_baseToolsByName.clear();
} }
Providers::ProviderInstanceFactory *AgentFactory::instanceFactory() const noexcept Providers::ProviderInstanceFactory *AgentFactory::instanceFactory() const noexcept
@@ -361,4 +398,37 @@ void AgentFactory::clearAgentProviderOverride(const QString &name)
writeProviderOverride(name, QString()); writeProviderOverride(name, QString());
} }
bool AgentFactory::setAgentToolsOverride(
const QString &name, std::optional<bool> 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<bool> AgentFactory::agentToolsOverride(const QString &name) const
{
return readToolsOverride(name);
}
void AgentFactory::clearAgentToolsOverride(const QString &name)
{
writeToolsOverride(name, std::nullopt);
}
} // namespace QodeAssist } // namespace QodeAssist

View File

@@ -4,6 +4,7 @@
#pragma once #pragma once
#include <optional>
#include <vector> #include <vector>
#include <QHash> #include <QHash>
@@ -60,18 +61,25 @@ public:
[[nodiscard]] QString agentProviderOverride(const QString &name) const; [[nodiscard]] QString agentProviderOverride(const QString &name) const;
void clearAgentProviderOverride(const QString &name); void clearAgentProviderOverride(const QString &name);
bool setAgentToolsOverride(
const QString &name, std::optional<bool> enabled, QString *error = nullptr);
[[nodiscard]] std::optional<bool> agentToolsOverride(const QString &name) const;
void clearAgentToolsOverride(const QString &name);
[[nodiscard]] Providers::ProviderInstanceFactory *instanceFactory() const noexcept; [[nodiscard]] Providers::ProviderInstanceFactory *instanceFactory() const noexcept;
signals: signals:
void agentsChanged(); void agentsChanged();
void agentModelChanged(const QString &name); void agentModelChanged(const QString &name);
void agentProviderChanged(const QString &name); void agentProviderChanged(const QString &name);
void agentToolsChanged(const QString &name);
private: private:
std::vector<AgentConfig> m_configs; std::vector<AgentConfig> m_configs;
QHash<QString, qsizetype> m_indexByName; QHash<QString, qsizetype> m_indexByName;
QHash<QString, QString> m_baseModelByName; QHash<QString, QString> m_baseModelByName;
QHash<QString, QString> m_baseProviderByName; QHash<QString, QString> m_baseProviderByName;
QHash<QString, bool> m_baseToolsByName;
QPointer<Providers::ProviderInstanceFactory> m_instanceFactory; QPointer<Providers::ProviderInstanceFactory> m_instanceFactory;
QPointer<Providers::ProviderSecretsStore> m_secrets; QPointer<Providers::ProviderSecretsStore> m_secrets;
}; };