diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index 17ad1cb..a6a1bf4 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -13,12 +13,12 @@ #include #include +#include #include #include #include -#include +#include #include -#include #include "sources/common/ContextData.hpp" #include "CodeHandler.hpp" @@ -34,13 +34,11 @@ namespace QodeAssist { LLMClientInterface::LLMClientInterface( const Settings::GeneralSettings &generalSettings, const Settings::CodeCompletionSettings &completeSettings, - SessionManager &sessionManager, AgentFactory &agentFactory, Context::IDocumentReader &documentReader, IRequestPerformanceLogger &performanceLogger) : m_generalSettings(generalSettings) , m_completeSettings(completeSettings) - , m_sessionManager(sessionManager) , m_agentFactory(agentFactory) , m_documentReader(documentReader) , m_performanceLogger(performanceLogger) @@ -63,32 +61,24 @@ void LLMClientInterface::startImpl() emit started(); } -void LLMClientInterface::onSessionEvent(const QString &requestId, const ResponseEvent &event) +void LLMClientInterface::onCompletionFinished(const QString &requestId) { auto it = m_activeRequests.find(requestId); if (it == m_activeRequests.end()) return; - if (event.kind() == ResponseEvent::Kind::TextDelta) { - if (const auto *delta = event.as()) - it.value().accumulated += delta->text; + QString fullText; + if (Session *session = it.value().session) { + if (auto *history = session->history(); history && !history->isEmpty()) + fullText = history->messages().back().text(); } -} - -void LLMClientInterface::onSessionFinished(const QString &requestId) -{ - auto it = m_activeRequests.find(requestId); - if (it == m_activeRequests.end()) - return; - - const QString fullText = it.value().accumulated; const QJsonObject originalRequest = it.value().originalRequest; sendCompletionToClient(fullText, originalRequest, true); finishRequest(requestId); } -void LLMClientInterface::onSessionFailed(const QString &requestId, const QString &error) +void LLMClientInterface::onCompletionFailed(const QString &requestId, const QString &error) { auto it = m_activeRequests.find(requestId); if (it == m_activeRequests.end()) @@ -120,7 +110,7 @@ void LLMClientInterface::finishRequest(const QString &requestId) m_performanceLogger.endTimeMeasurement(requestId); if (session) - m_sessionManager.removeSession(session); + session->deleteLater(); } void LLMClientInterface::sendData(const QByteArray &data) @@ -158,8 +148,10 @@ void LLMClientInterface::handleCancelRequest() for (auto it = requests.begin(); it != requests.end(); ++it) { m_performanceLogger.endTimeMeasurement(it.key()); - if (it.value().session) - m_sessionManager.removeSession(it.value().session); + if (Session *session = it.value().session) { + session->cancel(); + session->deleteLater(); + } } LOG_MESSAGE("All requests cancelled and state cleared"); @@ -252,11 +244,20 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) return; } - QString sessionError; - Session *session = m_sessionManager.createSession(agentName, &sessionError); - if (!session) { - LOG_MESSAGE(sessionError); - sendErrorResponse(request, sessionError); + QString agentError; + Agent *agent = m_agentFactory.create(agentName, /*parent=*/nullptr, &agentError); + if (!agent) { + LOG_MESSAGE(agentError); + sendErrorResponse(request, agentError); + return; + } + + auto *session = new Session(agent, this); + if (!session->isValid()) { + const QString error = session->invalidReason(); + delete session; + LOG_MESSAGE(error); + sendErrorResponse(request, error); return; } @@ -272,14 +273,11 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) if (!editorContext.isEmpty()) context.systemPrompt = editorContext; - connect(session, &Session::event, this, [this, session](const ResponseEvent &event) { - onSessionEvent(requestIdForSession(session), event); - }); connect(session, &Session::finished, this, [this, session](const LLMQore::RequestID &, const QString &) { - onSessionFinished(requestIdForSession(session)); + onCompletionFinished(requestIdForSession(session)); }); connect(session, &Session::failed, this, [this, session](const LLMQore::RequestID &, const QString &error) { - onSessionFailed(requestIdForSession(session), error); + onCompletionFailed(requestIdForSession(session), error); }); if (auto *client = session->client()) @@ -288,14 +286,14 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) const LLMQore::RequestID requestId = session->sendCompletion(std::move(context)); if (requestId.isEmpty()) { - m_sessionManager.removeSession(session); + session->deleteLater(); QString error = QString("Failed to start completion request for agent: %1").arg(agentName); LOG_MESSAGE(error); sendErrorResponse(request, error); return; } - m_activeRequests[requestId] = {request, session, QString()}; + m_activeRequests[requestId] = {request, session}; m_performanceLogger.startTimeMeasurement(requestId); } diff --git a/LLMClientInterface.hpp b/LLMClientInterface.hpp index d51c0f4..f7783d1 100644 --- a/LLMClientInterface.hpp +++ b/LLMClientInterface.hpp @@ -22,10 +22,8 @@ class QNetworkAccessManager; namespace QodeAssist { -class SessionManager; class AgentFactory; class Session; -class ResponseEvent; namespace Templates { struct ContextData; @@ -39,7 +37,6 @@ public: LLMClientInterface( const Settings::GeneralSettings &generalSettings, const Settings::CodeCompletionSettings &completeSettings, - SessionManager &sessionManager, AgentFactory &agentFactory, Context::IDocumentReader &documentReader, IRequestPerformanceLogger &performanceLogger); @@ -69,9 +66,8 @@ private: void handleCancelRequest(); void sendErrorResponse(const QJsonObject &request, const QString &errorMessage); - void onSessionEvent(const QString &requestId, const ResponseEvent &event); - void onSessionFinished(const QString &requestId); - void onSessionFailed(const QString &requestId, const QString &error); + void onCompletionFinished(const QString &requestId); + void onCompletionFailed(const QString &requestId, const QString &error); void finishRequest(const QString &requestId); QString requestIdForSession(Session *session) const; @@ -79,7 +75,6 @@ private: { QJsonObject originalRequest; QPointer session; - QString accumulated; }; Templates::ContextData prepareContext( @@ -89,7 +84,6 @@ private: const Settings::CodeCompletionSettings &m_completeSettings; const Settings::GeneralSettings &m_generalSettings; - SessionManager &m_sessionManager; AgentFactory &m_agentFactory; Context::IDocumentReader &m_documentReader; IRequestPerformanceLogger &m_performanceLogger; diff --git a/QuickRefactorHandler.cpp b/QuickRefactorHandler.cpp index 45d354f..7f9af1a 100644 --- a/QuickRefactorHandler.cpp +++ b/QuickRefactorHandler.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -177,7 +178,7 @@ void QuickRefactorHandler::prepareAndSendRequest( session->systemPrompt()->setLayer( QStringLiteral("refactor"), buildSystemPrompt(editor, range)); - provider->client()->setTransferTimeout( + client->setTransferTimeout( static_cast(Settings::generalSettings().requestTimeout() * 1000)); m_isRefactoringInProgress = true; diff --git a/bench/main.cpp b/bench/main.cpp index 05b8d2e..62d9f58 100644 --- a/bench/main.cpp +++ b/bench/main.cpp @@ -33,7 +33,6 @@ #include #include #include -#include using namespace QodeAssist; @@ -259,7 +258,6 @@ int main(int argc, char *argv[]) auto *instances = new Providers::ProviderInstanceFactory(&app); auto *secrets = new Providers::ProviderSecretsStore(&app); auto *agentFactory = new AgentFactory(instances, secrets, &app); - auto *sessions = new SessionManager(agentFactory, &app); if (parser.isSet(listOpt)) { const QStringList names = agentFactory->configNames(); @@ -271,21 +269,26 @@ int main(int argc, char *argv[]) } QString error; - Session *session = nullptr; + Agent *agent = nullptr; if (parser.isSet(fileOpt)) { - Agent *agent = agentFactory->createFromFile(parser.value(fileOpt), &app, &error); - if (agent) - session = new Session(agent, &app); + agent = agentFactory->createFromFile(parser.value(fileOpt), &app, &error); } else if (parser.isSet(agentOpt)) { - session = sessions->createSession(parser.value(agentOpt), &error); + agent = agentFactory->create(parser.value(agentOpt), &app, &error); } else { err() << "Specify an agent with --agent or --file , or use --list.\n"; return 2; } - if (!session || !session->isValid()) { - err() << "Failed to create session: " - << (session ? session->invalidReason() : error) << "\n"; + if (!agent) { + err() << "Failed to create agent: " << error << "\n"; + return 1; + } + + const bool fimMode = parser.isSet(fimOpt); + + Session *session = new Session(agent, &app); + if (!session->isValid()) { + err() << "Failed to create session: " << session->invalidReason() << "\n"; return 1; } @@ -303,14 +306,14 @@ int main(int argc, char *argv[]) QString key = parser.value(apiKeyOpt); if (key.isEmpty()) { - const AgentConfig &cfg = session->agent()->config(); + const AgentConfig &cfg = agent->config(); const Providers::ProviderInstance *inst = instances->instanceByName(cfg.providerInstance); if (inst) key = resolveApiKey(envFile, inst->clientApi, inst->apiKeyRef); } - if (!key.isEmpty() && session->agent()->provider()) - session->agent()->provider()->setApiKey(key); + if (!key.isEmpty() && agent->provider()) + agent->provider()->setApiKey(key); } { @@ -340,23 +343,26 @@ int main(int argc, char *argv[]) const bool showThinking = !parser.isSet(noThinkingOpt); int exitCode = 0; - QObject::connect(session, &Session::event, &app, [showThinking](const ResponseEvent &ev) { - printEvent(ev, showThinking); - }); QObject::connect( - session, &Session::finished, &app, [&](const LLMQore::RequestID &, const QString &reason) { + session, &Session::event, &app, [showThinking](const ResponseEvent &ev) { + printEvent(ev, showThinking); + }); + QObject::connect( + session, &Session::finished, &app, + [&](const LLMQore::RequestID &, const QString &reason) { err() << "\n[done] stopReason=" << (reason.isEmpty() ? "" : reason) << "\n"; QCoreApplication::quit(); }); QObject::connect( - session, &Session::failed, &app, [&](const LLMQore::RequestID &, const QString &msg) { + session, &Session::failed, &app, + [&](const LLMQore::RequestID &, const QString &msg) { err() << "\n[failed] " << msg << "\n"; exitCode = 1; QCoreApplication::quit(); }); auto dispatch = [&] { - if (parser.isSet(fimOpt)) { + if (fimMode) { Templates::ContextData ctx; ctx.prefix = prompt; if (parser.isSet(suffixOpt)) diff --git a/qodeassist.cpp b/qodeassist.cpp index 3fb2a1c..72fc26d 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -225,9 +225,8 @@ public: m_agentsOptionsPage = Settings::createAgentsSettingsPage( m_agentFactory, m_agentsPageNavigator); - m_agentPipelinesPageNavigator = new Settings::AgentPipelinesPageNavigator(this); - m_agentPipelinesOptionsPage = Settings::createAgentPipelinesSettingsPage( - m_agentFactory, m_agentPipelinesPageNavigator, m_agentsPageNavigator); + Settings::generalSettings().setAgentPipelinesContext( + m_agentFactory, m_agentsPageNavigator); m_mcpServerManager = new Mcp::McpServerManager(this); m_mcpServerManager->init(); @@ -347,7 +346,6 @@ public: m_qodeAssistClient = new QodeAssistClient(new LLMClientInterface( Settings::generalSettings(), Settings::codeCompletionSettings(), - *m_sessionManager, *m_agentFactory, m_documentReader, m_performanceLogger)); @@ -533,8 +531,6 @@ private: QPointer m_sessionManager; QPointer m_agentsPageNavigator; std::unique_ptr m_agentsOptionsPage; - QPointer m_agentPipelinesPageNavigator; - std::unique_ptr m_agentPipelinesOptionsPage; }; } // namespace QodeAssist::Internal diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index 821784b..87f5d51 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -6,16 +6,14 @@ #include #include +#include #include -#include #include #include #include #include #include -#include #include -#include #include #include @@ -39,6 +37,192 @@ GeneralSettings &generalSettings() return settings; } +namespace { + +constexpr int kSaveDebounceMs = 300; + +struct SlotMeta +{ + const char *title; + const char *hint; +}; + +const SlotMeta kSlotMeta[] = { + {TrConstants::CODE_COMPLETION, TrConstants::SLOT_HINT_CODE_COMPLETION}, + {TrConstants::CHAT_ASSISTANT, TrConstants::SLOT_HINT_CHAT_ASSISTANT}, + {TrConstants::CHAT_COMPRESSION, TrConstants::SLOT_HINT_CHAT_COMPRESSION}, + {TrConstants::QUICK_REFACTOR, TrConstants::SLOT_HINT_QUICK_REFACTOR}, +}; + +class AgentPipelinesWidget : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(AgentPipelinesWidget) +public: + AgentPipelinesWidget( + const QPointer &agentFactory, + const QPointer &agentsNavigator, + QWidget *parent = nullptr) + : QWidget(parent) + , m_agentFactory(agentFactory) + , m_agentsNavigator(agentsNavigator) + { + m_titleLabel = new QLabel(Tr::tr(TrConstants::AGENT_PIPELINES), this); + QFont tf = m_titleLabel->font(); + tf.setBold(true); + tf.setPixelSize(13); + m_titleLabel->setFont(tf); + + m_resetBtn = new QPushButton(Tr::tr(TrConstants::RESET_TO_DEFAULTS), this); + + auto *headerRow = new QHBoxLayout; + headerRow->setContentsMargins(0, 0, 0, 0); + headerRow->setSpacing(8); + headerRow->addWidget(m_titleLabel); + headerRow->addStretch(1); + headerRow->addWidget(m_resetBtn); + + auto *headerSep = new QFrame(this); + headerSep->setFrameShape(QFrame::HLine); + headerSep->setFrameShadow(QFrame::Sunken); + + m_loadWarning = new Utils::InfoLabel({}, Utils::InfoLabel::Warning, this); + m_loadWarning->setElideMode(Qt::ElideNone); + m_loadWarning->setWordWrap(true); + m_loadWarning->setVisible(false); + + m_rosters[0] = new AgentRosterWidget(this); + m_rosters[1] = new AgentRosterWidget(this); + m_rosters[2] = new AgentRosterWidget(this); + m_rosters[3] = new AgentRosterWidget(this); + + for (int i = 0; i < kRosterCount; ++i) + m_rosters[i]->setSlot(Tr::tr(kSlotMeta[i].title), Tr::tr(kSlotMeta[i].hint), {}); + + auto *root = new QVBoxLayout(this); + root->setContentsMargins(0, 0, 0, 0); + root->setSpacing(12); + root->addLayout(headerRow); + root->addWidget(headerSep); + root->addWidget(m_loadWarning); + for (int i = 0; i < kRosterCount; ++i) + root->addWidget(m_rosters[i]); + + m_saveDebounce = new QTimer(this); + m_saveDebounce->setSingleShot(true); + m_saveDebounce->setInterval(kSaveDebounceMs); + connect(m_saveDebounce, &QTimer::timeout, this, [this]() { persistRosters(); }); + + loadFromSettings(); + + connect(m_resetBtn, &QPushButton::clicked, this, &AgentPipelinesWidget::onReset); + + for (int i = 0; i < kRosterCount; ++i) { + connect(m_rosters[i], &AgentRosterWidget::editAgentRequested, this, + &AgentPipelinesWidget::onEditAgent); + connect(m_rosters[i], &AgentRosterWidget::rosterChanged, this, + [this](const QStringList &) { m_saveDebounce->start(); }); + } + } + + ~AgentPipelinesWidget() override + { + if (m_saveDebounce && m_saveDebounce->isActive()) { + m_saveDebounce->stop(); + persistRosters(); + } + } + +private: + static constexpr int kRosterCount = 4; + + void persistRosters() + { + PipelineRosters rosters; + rosters.codeCompletion = m_rosters[0]->roster(); + rosters.chatAssistant = m_rosters[1]->roster(); + rosters.chatCompression = m_rosters[2]->roster(); + rosters.quickRefactor = m_rosters[3]->roster(); + QString err; + if (!PipelinesConfig::save(rosters, &err)) { + LOG_MESSAGE(QStringLiteral("[Pipelines] save failed (%1): %2") + .arg(PipelinesConfig::filePath(), err)); + if (!m_saveErrorShown) { + m_saveErrorShown = true; + QMessageBox::warning( + Core::ICore::dialogParent(), + Tr::tr(TrConstants::AGENT_PIPELINES), + tr("Failed to save pipelines.toml:\n%1\n\n" + "Further save failures will only be logged.") + .arg(err)); + } + } else { + m_saveErrorShown = false; + } + } + + void onReset() + { + const auto reply = QMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr(TrConstants::RESET_SETTINGS), + Tr::tr(TrConstants::CONFIRMATION), + QMessageBox::Yes | QMessageBox::No); + + if (reply != QMessageBox::Yes) + return; + + QString err; + if (!PipelinesConfig::save(PipelineRosters::defaults(), &err)) + LOG_MESSAGE(QStringLiteral("[Pipelines] failed to reset rosters: %1").arg(err)); + + m_saveErrorShown = false; + loadFromSettings(); + } + + void onEditAgent(const QString &name) + { + if (m_agentsNavigator) + m_agentsNavigator->requestSelectAgent(name); + + showSettings(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID); + } + + void loadFromSettings() + { + const PipelinesLoadResult lr = PipelinesConfig::load(); + const bool broken = lr.status == PipelinesLoadStatus::ParseError + || lr.status == PipelinesLoadStatus::SchemaError; + if (broken) { + m_loadWarning->setText( + tr("pipelines.toml has issues — using defaults for affected entries:\n%1\n" + "Changes you make here will overwrite the file.") + .arg(lr.message)); + } + m_loadWarning->setVisible(broken); + + AgentFactory *factory = m_agentFactory.data(); + m_rosters[0]->setRoster(lr.rosters.codeCompletion, factory); + m_rosters[1]->setRoster(lr.rosters.chatAssistant, factory); + m_rosters[2]->setRoster(lr.rosters.chatCompression, factory); + m_rosters[3]->setRoster(lr.rosters.quickRefactor, factory); + } + + QPointer m_agentFactory; + QPointer m_agentsNavigator; + + QLabel *m_titleLabel = nullptr; + QPushButton *m_resetBtn = nullptr; + Utils::InfoLabel *m_loadWarning = nullptr; + + AgentRosterWidget *m_rosters[kRosterCount] = {}; + + QTimer *m_saveDebounce = nullptr; + bool m_saveErrorShown = false; +}; + +} // namespace + GeneralSettings::GeneralSettings() { setAutoApply(false); @@ -99,6 +283,8 @@ GeneralSettings::GeneralSettings() supportLinks->setOpenExternalLinks(true); supportLinks->setTextFormat(Qt::RichText); + auto *pipelines = new AgentPipelinesWidget(m_agentFactory, m_agentsNavigator); + return Column{ Row{supportLabel, supportLinks, Stretch{1}}, Space{8}, @@ -107,10 +293,19 @@ GeneralSettings::GeneralSettings() Row{enableCheckUpdate, Stretch{1}}, Space{8}, networkGroup, + Space{12}, + pipelines, Stretch{1}}; }); } +void GeneralSettings::setAgentPipelinesContext( + AgentFactory *agentFactory, AgentsPageNavigator *agentsNavigator) +{ + m_agentFactory = agentFactory; + m_agentsNavigator = agentsNavigator; +} + void GeneralSettings::setupConnections() { connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { @@ -183,244 +378,6 @@ void showSettings(const Utils::Id page, Utils::Id item) #endif } -AgentPipelinesPageNavigator::AgentPipelinesPageNavigator(QObject *parent) - : QObject(parent) -{} - -namespace { - -constexpr int kSaveDebounceMs = 300; - -struct SlotMeta -{ - const char *title; - const char *hint; -}; - -const SlotMeta kSlotMeta[] = { - {TrConstants::CODE_COMPLETION, TrConstants::SLOT_HINT_CODE_COMPLETION}, - {TrConstants::CHAT_ASSISTANT, TrConstants::SLOT_HINT_CHAT_ASSISTANT}, - {TrConstants::CHAT_COMPRESSION, TrConstants::SLOT_HINT_CHAT_COMPRESSION}, - {TrConstants::QUICK_REFACTOR, TrConstants::SLOT_HINT_QUICK_REFACTOR}, -}; - -class AgentPipelinesPageWidget : public Core::IOptionsPageWidget -{ - Q_OBJECT - Q_DISABLE_COPY_MOVE(AgentPipelinesPageWidget) -public: - AgentPipelinesPageWidget( - const QPointer &agentFactory, - const QPointer &navigator, - const QPointer &agentsNavigator) - : m_agentFactory(agentFactory) - , m_navigator(navigator) - , m_agentsNavigator(agentsNavigator) - { - m_titleLabel = new QLabel(Tr::tr(TrConstants::AGENT_PIPELINES), this); - QFont tf = m_titleLabel->font(); - tf.setBold(true); - tf.setPixelSize(13); - m_titleLabel->setFont(tf); - - m_resetBtn = new QPushButton(Tr::tr(TrConstants::RESET_TO_DEFAULTS), this); - - auto *headerRow = new QHBoxLayout; - headerRow->setContentsMargins(0, 0, 0, 0); - headerRow->setSpacing(8); - headerRow->addWidget(m_titleLabel); - headerRow->addStretch(1); - headerRow->addWidget(m_resetBtn); - - auto *headerSep = new QFrame(this); - headerSep->setFrameShape(QFrame::HLine); - headerSep->setFrameShadow(QFrame::Sunken); - - m_rosters[0] = new AgentRosterWidget(this); - m_rosters[1] = new AgentRosterWidget(this); - m_rosters[2] = new AgentRosterWidget(this); - m_rosters[3] = new AgentRosterWidget(this); - - for (int i = 0; i < kRosterCount; ++i) - m_rosters[i]->setSlot(Tr::tr(kSlotMeta[i].title), Tr::tr(kSlotMeta[i].hint), {}); - - auto *content = new QWidget(this); - auto *contentLay = new QVBoxLayout(content); - contentLay->setContentsMargins(0, 0, 0, 0); - contentLay->setSpacing(12); - for (int i = 0; i < kRosterCount; ++i) - contentLay->addWidget(m_rosters[i]); - contentLay->addStretch(1); - - auto *scroll = new QScrollArea(this); - scroll->setWidgetResizable(true); - scroll->setFrameShape(QFrame::NoFrame); - scroll->setWidget(content); - - auto *root = new QVBoxLayout(this); - root->setContentsMargins(8, 8, 8, 8); - root->setSpacing(6); - root->addLayout(headerRow); - root->addWidget(headerSep); - root->addWidget(scroll, 1); - - m_saveDebounce = new QTimer(this); - m_saveDebounce->setSingleShot(true); - m_saveDebounce->setInterval(kSaveDebounceMs); - connect(m_saveDebounce, &QTimer::timeout, this, [this]() { persistRosters(); }); - - loadFromSettings(); - - connect(m_resetBtn, &QPushButton::clicked, this, &AgentPipelinesPageWidget::onReset); - - for (int i = 0; i < kRosterCount; ++i) { - connect(m_rosters[i], &AgentRosterWidget::editAgentRequested, this, - &AgentPipelinesPageWidget::onEditAgent); - connect(m_rosters[i], &AgentRosterWidget::rosterChanged, this, - [this](const QStringList &) { m_saveDebounce->start(); }); - } - } - - ~AgentPipelinesPageWidget() override - { - if (m_saveDebounce && m_saveDebounce->isActive()) { - m_saveDebounce->stop(); - persistRosters(); - } - } - - void apply() final - { - if (m_saveDebounce && m_saveDebounce->isActive()) - m_saveDebounce->stop(); - persistRosters(); - } - -private: - static constexpr int kRosterCount = 4; - - void persistRosters() - { - PipelineRosters rosters; - rosters.codeCompletion = m_rosters[0]->roster(); - rosters.chatAssistant = m_rosters[1]->roster(); - rosters.chatCompression = m_rosters[2]->roster(); - rosters.quickRefactor = m_rosters[3]->roster(); - QString err; - if (!PipelinesConfig::save(rosters, &err)) { - LOG_MESSAGE(QStringLiteral("[Pipelines] save failed (%1): %2") - .arg(PipelinesConfig::filePath(), err)); - if (!m_saveErrorShown) { - m_saveErrorShown = true; - QMessageBox::warning( - Core::ICore::dialogParent(), - Tr::tr(TrConstants::AGENT_PIPELINES), - tr("Failed to save pipelines.toml:\n%1\n\n" - "Further save failures will only be logged.") - .arg(err)); - } - } else { - m_saveErrorShown = false; - } - } - - void onReset() - { - const auto reply = QMessageBox::question( - Core::ICore::dialogParent(), - Tr::tr(TrConstants::RESET_SETTINGS), - Tr::tr(TrConstants::CONFIRMATION), - QMessageBox::Yes | QMessageBox::No); - - if (reply != QMessageBox::Yes) - return; - - QString err; - if (!PipelinesConfig::save(PipelineRosters::defaults(), &err)) - LOG_MESSAGE(QStringLiteral("[Pipelines] failed to reset rosters: %1").arg(err)); - - m_saveErrorShown = false; - loadFromSettings(); - } - - void onEditAgent(const QString &name) - { - if (m_agentsNavigator) - m_agentsNavigator->requestSelectAgent(name); - if (m_navigator) - emit m_navigator->editAgentRequested(name); - -#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 83) - Core::ICore::showSettings(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID); -#else - Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID); -#endif - } - - void loadFromSettings() - { - const PipelinesLoadResult lr = PipelinesConfig::load(); - if (lr.status == PipelinesLoadStatus::ParseError - || lr.status == PipelinesLoadStatus::SchemaError) { - QMessageBox::warning( - Core::ICore::dialogParent(), - Tr::tr(TrConstants::AGENT_PIPELINES), - tr("pipelines.toml has issues — using defaults for affected entries:\n%1\n\n" - "Click OK to continue. Changes you make here will overwrite the file.") - .arg(lr.message)); - } - - AgentFactory *factory = m_agentFactory.data(); - m_rosters[0]->setRoster(lr.rosters.codeCompletion, factory); - m_rosters[1]->setRoster(lr.rosters.chatAssistant, factory); - m_rosters[2]->setRoster(lr.rosters.chatCompression, factory); - m_rosters[3]->setRoster(lr.rosters.quickRefactor, factory); - } - - QPointer m_agentFactory; - QPointer m_navigator; - QPointer m_agentsNavigator; - - QLabel *m_titleLabel = nullptr; - QPushButton *m_resetBtn = nullptr; - - AgentRosterWidget *m_rosters[kRosterCount] = {}; - - QTimer *m_saveDebounce = nullptr; - bool m_saveErrorShown = false; -}; - -class AgentPipelinesOptionsPage final : public Core::IOptionsPage -{ -public: - AgentPipelinesOptionsPage( - AgentFactory *agentFactory, - AgentPipelinesPageNavigator *navigator, - AgentsPageNavigator *agentsNavigator) - { - setId(Constants::QODE_ASSIST_AGENT_PIPELINES_PAGE_ID); - setDisplayName(Tr::tr(TrConstants::AGENT_PIPELINES)); - setCategory(Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY); - const QPointer factoryPtr(agentFactory); - const QPointer navPtr(navigator); - const QPointer agentsNavPtr(agentsNavigator); - setWidgetCreator([factoryPtr, navPtr, agentsNavPtr] { - return new AgentPipelinesPageWidget(factoryPtr, navPtr, agentsNavPtr); - }); - } -}; - -} // namespace - -std::unique_ptr createAgentPipelinesSettingsPage( - AgentFactory *agentFactory, - AgentPipelinesPageNavigator *navigator, - AgentsPageNavigator *agentsNavigator) -{ - return std::make_unique( - agentFactory, navigator, agentsNavigator); -} - } // namespace QodeAssist::Settings #include "GeneralSettings.moc" diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp index e9d8032..a6094b7 100644 --- a/settings/GeneralSettings.hpp +++ b/settings/GeneralSettings.hpp @@ -4,30 +4,28 @@ #pragma once -#include - -#include -#include +#include #include #include "ButtonAspect.hpp" -namespace Core { -class IOptionsPage; -} - namespace QodeAssist { class AgentFactory; } namespace QodeAssist::Settings { +class AgentsPageNavigator; + class GeneralSettings : public Utils::AspectContainer { public: GeneralSettings(); + void setAgentPipelinesContext( + AgentFactory *agentFactory, AgentsPageNavigator *agentsNavigator); + Utils::BoolAspect enableQodeAssist{this}; Utils::BoolAspect enableLogging{this}; Utils::BoolAspect enableCheckUpdate{this}; @@ -40,6 +38,9 @@ public: private: void setupConnections(); void resetPageToDefaults(); + + QPointer m_agentFactory; + QPointer m_agentsNavigator; }; GeneralSettings &generalSettings(); @@ -47,22 +48,4 @@ GeneralSettings &generalSettings(); void showSettings(const Utils::Id page); void showSettings(const Utils::Id page, Utils::Id item); -class AgentsPageNavigator; - -class AgentPipelinesPageNavigator : public QObject -{ - Q_OBJECT - Q_DISABLE_COPY_MOVE(AgentPipelinesPageNavigator) -public: - explicit AgentPipelinesPageNavigator(QObject *parent = nullptr); - -signals: - void editAgentRequested(const QString &agentName); -}; - -std::unique_ptr createAgentPipelinesSettingsPage( - AgentFactory *agentFactory, - AgentPipelinesPageNavigator *navigator, - AgentsPageNavigator *agentsNavigator); - } // namespace QodeAssist::Settings diff --git a/settings/SettingsConstants.hpp b/settings/SettingsConstants.hpp index 1cc7c96..d095ab8 100644 --- a/settings/SettingsConstants.hpp +++ b/settings/SettingsConstants.hpp @@ -143,9 +143,6 @@ const char QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID[] = "QodeAssist.7ProviderSettin // Agents Settings Page ID const char QODE_ASSIST_AGENTS_SETTINGS_PAGE_ID[] = "QodeAssist.8AgentsSettingsPageId"; -// Agent Pipelines (experimental) settings -const char QODE_ASSIST_AGENT_PIPELINES_PAGE_ID[] = "QodeAssist.9AgentPipelinesPageId"; - // Provider API Keys const char OPEN_ROUTER_API_KEY[] = "QodeAssist.openRouterApiKey"; const char OPEN_ROUTER_API_KEY_HISTORY[] = "QodeAssist.openRouterApiKeyHistory"; diff --git a/sources/Session/Session.cpp b/sources/Session/Session.cpp index a285bd0..23df74b 100644 --- a/sources/Session/Session.cpp +++ b/sources/Session/Session.cpp @@ -150,6 +150,15 @@ LLMQore::RequestID Session::sendText(const QString &text) return send(std::move(blocks)); } +LLMQore::RequestID Session::sendCompletion(Templates::ContextData ctx) +{ + if (!isValid()) + return {}; + if (isInFlight()) + cancel(); + return dispatchContext(std::move(ctx), /*tools=*/false, /*thinking=*/false); +} + LLMQore::RequestID Session::send( std::vector> userBlocks, std::optional toolsOverride, @@ -185,49 +194,9 @@ void Session::cancel() emit failed(id, QStringLiteral("Cancelled by user")); } -LLMQore::RequestID Session::sendCompletion(Templates::ContextData ctx) -{ - if (!isValid()) - return {}; - if (isInFlight()) - cancel(); - - if (m_history) - m_history->clear(); - - auto *provider = m_agent->provider(); - auto *tmpl = m_agent->promptTemplate(); - const auto &cfg = m_agent->config(); - - const QString rolePrompt = m_systemPrompt ? m_systemPrompt->compose() : QString(); - if (!rolePrompt.isEmpty()) { - ctx.systemPrompt = (ctx.systemPrompt && !ctx.systemPrompt->isEmpty()) - ? rolePrompt + QStringLiteral("\n\n") + *ctx.systemPrompt - : rolePrompt; - } - - QJsonObject payload{{QStringLiteral("model"), cfg.model}}; - if (!provider->prepareRequest(payload, tmpl, ctx, /*tools=*/false, /*thinking=*/false)) - return {}; - - QString endpoint = cfg.endpoint; - endpoint.replace(QStringLiteral("${MODEL}"), cfg.model); - const auto id = provider->sendRequest(QUrl(provider->url()), payload, endpoint); - if (id.isEmpty()) - return {}; - - m_inFlight = id; - if (m_router) - m_router->beginRequest(id); - emit started(id); - return id; -} - LLMQore::RequestID Session::dispatch( std::optional toolsOverride, std::optional thinkingOverride) { - auto *provider = m_agent->provider(); - auto *tmpl = m_agent->promptTemplate(); const auto &cfg = m_agent->config(); const QString renderedContext = renderAgentContext(); @@ -236,11 +205,19 @@ LLMQore::RequestID Session::dispatch( else m_systemPrompt->setLayer(QStringLiteral("agent.system"), renderedContext); - Templates::ContextData ctx = toLegacyContext(); - QJsonObject payload{{QStringLiteral("model"), cfg.model}}; - const bool tools = toolsOverride.value_or(cfg.enableTools); const bool thinking = thinkingOverride.value_or(cfg.enableThinking); + return dispatchContext(toLegacyContext(), tools, thinking); +} + +LLMQore::RequestID Session::dispatchContext( + Templates::ContextData ctx, bool tools, bool thinking) +{ + auto *provider = m_agent->provider(); + auto *tmpl = m_agent->promptTemplate(); + const auto &cfg = m_agent->config(); + + QJsonObject payload{{QStringLiteral("model"), cfg.model}}; if (!provider->prepareRequest(payload, tmpl, ctx, tools, thinking)) return {}; diff --git a/sources/Session/Session.hpp b/sources/Session/Session.hpp index d3e7e1a..351d53d 100644 --- a/sources/Session/Session.hpp +++ b/sources/Session/Session.hpp @@ -70,7 +70,7 @@ public: LLMQore::RequestID sendText(const QString &text); LLMQore::RequestID sendCompletion(Templates::ContextData ctx); - + void cancel(); signals: @@ -87,6 +87,7 @@ private: LLMQore::RequestID dispatch( std::optional toolsOverride = std::nullopt, std::optional thinkingOverride = std::nullopt); + LLMQore::RequestID dispatchContext(Templates::ContextData ctx, bool tools, bool thinking); Templates::ContextData toLegacyContext() const; Agent *m_agent = nullptr; // child if non-null