mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-14 02:09:22 -04:00
refactor: Remove project rules
This commit is contained in:
@@ -3,6 +3,7 @@ add_library(Session STATIC
|
||||
MessageSerializer.hpp MessageSerializer.cpp
|
||||
PluginBlocks.hpp
|
||||
LLMRequest.hpp
|
||||
ErrorInfo.hpp
|
||||
ResponseEvent.hpp
|
||||
ConversationHistory.hpp ConversationHistory.cpp
|
||||
ResponseRouter.hpp ResponseRouter.cpp
|
||||
|
||||
61
sources/Session/ErrorInfo.hpp
Normal file
61
sources/Session/ErrorInfo.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
enum class ErrorCategory {
|
||||
Config,
|
||||
Auth,
|
||||
Network,
|
||||
Provider,
|
||||
Validation,
|
||||
Tool,
|
||||
};
|
||||
|
||||
struct ErrorInfo
|
||||
{
|
||||
ErrorCategory category = ErrorCategory::Provider;
|
||||
QString message;
|
||||
QString providerDetail;
|
||||
|
||||
bool isEmpty() const noexcept { return message.isEmpty(); }
|
||||
};
|
||||
|
||||
[[nodiscard]] inline ErrorInfo makeError(
|
||||
ErrorCategory category, QString message, QString providerDetail = QString())
|
||||
{
|
||||
return ErrorInfo{category, std::move(message), std::move(providerDetail)};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline ErrorCategory categorizeProviderError(const QString &raw)
|
||||
{
|
||||
const QString text = raw.toLower();
|
||||
|
||||
const auto contains = [&text](const char *needle) {
|
||||
return text.contains(QLatin1String(needle));
|
||||
};
|
||||
|
||||
if (contains("401") || contains("403") || contains("unauthorized")
|
||||
|| contains("forbidden") || contains("api key") || contains("apikey")
|
||||
|| contains("authentication") || contains("invalid token"))
|
||||
return ErrorCategory::Auth;
|
||||
|
||||
if (contains("timeout") || contains("timed out") || contains("connection")
|
||||
|| contains("could not resolve") || contains("unreachable")
|
||||
|| contains("network") || contains("ssl") || contains("refused"))
|
||||
return ErrorCategory::Network;
|
||||
|
||||
return ErrorCategory::Provider;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
Q_DECLARE_METATYPE(QodeAssist::ErrorInfo)
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "ErrorInfo.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
namespace ResponseEvents {
|
||||
@@ -45,6 +47,7 @@ struct ToolCallEnd
|
||||
struct ToolResult
|
||||
{
|
||||
QString toolUseId;
|
||||
QString name;
|
||||
QString text;
|
||||
bool isError = false;
|
||||
};
|
||||
@@ -53,11 +56,14 @@ struct Usage
|
||||
{
|
||||
int inputTokens = 0;
|
||||
int outputTokens = 0;
|
||||
int cachedTokens = 0;
|
||||
int reasoningTokens = 0;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
QString message;
|
||||
ErrorCategory category = ErrorCategory::Provider;
|
||||
};
|
||||
|
||||
struct MessageStop
|
||||
@@ -128,21 +134,27 @@ public:
|
||||
Kind::ToolCallEnd, ResponseEvents::ToolCallEnd{std::move(id), std::move(finalArgs)}};
|
||||
}
|
||||
|
||||
static ResponseEvent toolResult(QString toolUseId, QString text, bool isError = false)
|
||||
static ResponseEvent toolResult(
|
||||
QString toolUseId, QString name, QString text, bool isError = false)
|
||||
{
|
||||
return {
|
||||
Kind::ToolResult,
|
||||
ResponseEvents::ToolResult{std::move(toolUseId), std::move(text), isError}};
|
||||
ResponseEvents::ToolResult{
|
||||
std::move(toolUseId), std::move(name), std::move(text), isError}};
|
||||
}
|
||||
|
||||
static ResponseEvent usage(int inputTokens, int outputTokens)
|
||||
static ResponseEvent usage(
|
||||
int inputTokens, int outputTokens, int cachedTokens = 0, int reasoningTokens = 0)
|
||||
{
|
||||
return {Kind::Usage, ResponseEvents::Usage{inputTokens, outputTokens}};
|
||||
return {
|
||||
Kind::Usage,
|
||||
ResponseEvents::Usage{inputTokens, outputTokens, cachedTokens, reasoningTokens}};
|
||||
}
|
||||
|
||||
static ResponseEvent error(QString message)
|
||||
static ResponseEvent error(
|
||||
QString message, ErrorCategory category = ErrorCategory::Provider)
|
||||
{
|
||||
return {Kind::Error, ResponseEvents::Error{std::move(message)}};
|
||||
return {Kind::Error, ResponseEvents::Error{std::move(message), category}};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -79,7 +79,7 @@ void ResponseRouter::ensureAssistantOpen()
|
||||
if (m_assistantOpen && !m_inToolResults)
|
||||
return;
|
||||
if (m_history)
|
||||
m_history->append(Message(Message::Role::Assistant));
|
||||
m_history->append(Message(Message::Role::Assistant, m_activeId));
|
||||
emit event(ResponseEvent::messageStart());
|
||||
m_assistantOpen = true;
|
||||
m_inToolResults = false;
|
||||
@@ -107,15 +107,19 @@ void ResponseRouter::onThinking(
|
||||
}
|
||||
|
||||
void ResponseRouter::onToolStarted(
|
||||
const LLMQore::RequestID &id, const QString &toolId, const QString &toolName)
|
||||
const LLMQore::RequestID &id,
|
||||
const QString &toolId,
|
||||
const QString &toolName,
|
||||
const QJsonObject &arguments)
|
||||
{
|
||||
if (id != m_activeId)
|
||||
return;
|
||||
ensureAssistantOpen();
|
||||
if (m_history)
|
||||
m_history->appendBlockToLast(
|
||||
std::make_unique<LLMQore::ToolUseContent>(toolId, toolName));
|
||||
std::make_unique<LLMQore::ToolUseContent>(toolId, toolName, arguments));
|
||||
emit event(ResponseEvent::toolCallStart(toolId, toolName));
|
||||
emit event(ResponseEvent::toolCallEnd(toolId, arguments));
|
||||
}
|
||||
|
||||
void ResponseRouter::onToolResultReady(
|
||||
@@ -124,7 +128,6 @@ void ResponseRouter::onToolResultReady(
|
||||
const QString &toolName,
|
||||
const QString &result)
|
||||
{
|
||||
Q_UNUSED(toolName);
|
||||
if (id != m_activeId)
|
||||
return;
|
||||
|
||||
@@ -141,7 +144,7 @@ void ResponseRouter::onToolResultReady(
|
||||
|
||||
m_assistantOpen = false;
|
||||
m_inToolResults = true;
|
||||
emit event(ResponseEvent::toolResult(toolId, result, /*isError=*/false));
|
||||
emit event(ResponseEvent::toolResult(toolId, toolName, result, /*isError=*/false));
|
||||
}
|
||||
|
||||
void ResponseRouter::onFinalized(
|
||||
@@ -149,6 +152,13 @@ void ResponseRouter::onFinalized(
|
||||
{
|
||||
if (id != m_activeId)
|
||||
return;
|
||||
if (info.usage) {
|
||||
emit event(ResponseEvent::usage(
|
||||
info.usage->promptTokens,
|
||||
info.usage->completionTokens,
|
||||
info.usage->cachedPromptTokens,
|
||||
info.usage->reasoningTokens));
|
||||
}
|
||||
emit event(ResponseEvent::messageStop(info.stopReason));
|
||||
endRequest();
|
||||
}
|
||||
@@ -157,7 +167,7 @@ void ResponseRouter::onFailed(const LLMQore::RequestID &id, const QString &err)
|
||||
{
|
||||
if (id != m_activeId)
|
||||
return;
|
||||
emit event(ResponseEvent::error(err));
|
||||
emit event(ResponseEvent::error(err, categorizeProviderError(err)));
|
||||
endRequest();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <LLMQore/BaseClient.hpp>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
@@ -41,7 +42,10 @@ private slots:
|
||||
void onThinking(
|
||||
const LLMQore::RequestID &id, const QString &thinking, const QString &signature);
|
||||
void onToolStarted(
|
||||
const LLMQore::RequestID &id, const QString &toolId, const QString &toolName);
|
||||
const LLMQore::RequestID &id,
|
||||
const QString &toolId,
|
||||
const QString &toolName,
|
||||
const QJsonObject &arguments);
|
||||
void onToolResultReady(
|
||||
const LLMQore::RequestID &id,
|
||||
const QString &toolId,
|
||||
|
||||
@@ -36,15 +36,9 @@ QString roleToLegacyString(Message::Role role)
|
||||
return QStringLiteral("user");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
[[maybe_unused]] const int kErrorInfoMetaTypeId = qRegisterMetaType<QodeAssist::ErrorInfo>();
|
||||
|
||||
Session::Session(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_history(new ConversationHistory(this))
|
||||
, m_systemPrompt(new SystemPromptBuilder(this))
|
||||
{
|
||||
m_invalidReason = QStringLiteral("Session: no agent attached");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Session::Session(Agent *agent, QObject *parent)
|
||||
: Session(agent, /*externalHistory=*/nullptr, parent)
|
||||
@@ -86,7 +80,7 @@ Session::Session(Agent *agent, ConversationHistory *externalHistory, QObject *pa
|
||||
Session::~Session()
|
||||
{
|
||||
if (isInFlight())
|
||||
cancel();
|
||||
teardownInFlight();
|
||||
}
|
||||
|
||||
bool Session::isValid() const noexcept
|
||||
@@ -104,6 +98,11 @@ bool Session::isInFlight() const noexcept
|
||||
return !m_inFlight.isEmpty();
|
||||
}
|
||||
|
||||
const ErrorInfo &Session::lastError() const noexcept
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
LLMQore::BaseClient *Session::client() const noexcept
|
||||
{
|
||||
auto *provider = m_agent ? m_agent->provider() : nullptr;
|
||||
@@ -127,21 +126,6 @@ void Session::setContextBindings(Templates::ContextRenderer::Bindings bindings)
|
||||
m_contextBindings = std::move(bindings);
|
||||
}
|
||||
|
||||
QString Session::renderAgentContext() const
|
||||
{
|
||||
if (!m_agent)
|
||||
return {};
|
||||
const auto &cfg = m_agent->config();
|
||||
if (cfg.systemPrompt.isEmpty())
|
||||
return {};
|
||||
QString err;
|
||||
QString rendered
|
||||
= Templates::ContextRenderer::render(cfg.systemPrompt, m_contextBindings, &err);
|
||||
if (!err.isEmpty())
|
||||
qWarning("[QodeAssist] agent.system render failed: %s", qUtf8Printable(err));
|
||||
return rendered;
|
||||
}
|
||||
|
||||
LLMQore::RequestID Session::sendText(const QString &text)
|
||||
{
|
||||
std::vector<std::unique_ptr<LLMQore::ContentBlock>> blocks;
|
||||
@@ -152,22 +136,27 @@ LLMQore::RequestID Session::sendText(const QString &text)
|
||||
|
||||
LLMQore::RequestID Session::sendCompletion(Templates::ContextData ctx)
|
||||
{
|
||||
if (!isValid())
|
||||
if (!isValid()) {
|
||||
m_lastError = makeError(ErrorCategory::Config, invalidReason());
|
||||
return {};
|
||||
}
|
||||
if (isInFlight())
|
||||
cancel();
|
||||
return dispatchContext(std::move(ctx), /*tools=*/false, /*thinking=*/false);
|
||||
return dispatchContext(std::move(ctx), /*tools=*/false);
|
||||
}
|
||||
|
||||
LLMQore::RequestID Session::send(
|
||||
std::vector<std::unique_ptr<LLMQore::ContentBlock>> userBlocks,
|
||||
std::optional<bool> toolsOverride,
|
||||
std::optional<bool> thinkingOverride)
|
||||
std::optional<bool> toolsOverride)
|
||||
{
|
||||
if (!isValid() || userBlocks.empty())
|
||||
if (!isValid()) {
|
||||
m_lastError = makeError(ErrorCategory::Config, invalidReason());
|
||||
return {};
|
||||
if (!m_history)
|
||||
}
|
||||
if (userBlocks.empty() || !m_history) {
|
||||
m_lastError = makeError(ErrorCategory::Validation, QStringLiteral("Session: nothing to send"));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (isInFlight())
|
||||
cancel();
|
||||
@@ -177,10 +166,20 @@ LLMQore::RequestID Session::send(
|
||||
msg.appendBlock(std::move(b));
|
||||
m_history->append(std::move(msg));
|
||||
|
||||
return dispatch(toolsOverride, thinkingOverride);
|
||||
return dispatch(toolsOverride);
|
||||
}
|
||||
|
||||
void Session::cancel()
|
||||
{
|
||||
if (m_inFlight.isEmpty())
|
||||
return;
|
||||
|
||||
const auto id = m_inFlight;
|
||||
teardownInFlight();
|
||||
emit cancelled(id);
|
||||
}
|
||||
|
||||
void Session::teardownInFlight()
|
||||
{
|
||||
if (m_inFlight.isEmpty())
|
||||
return;
|
||||
@@ -191,41 +190,61 @@ void Session::cancel()
|
||||
m_router->endRequest();
|
||||
if (m_agent && m_agent->provider())
|
||||
m_agent->provider()->cancelRequest(id);
|
||||
emit failed(id, QStringLiteral("Cancelled by user"));
|
||||
}
|
||||
|
||||
LLMQore::RequestID Session::dispatch(
|
||||
std::optional<bool> toolsOverride, std::optional<bool> thinkingOverride)
|
||||
LLMQore::RequestID Session::dispatch(std::optional<bool> toolsOverride)
|
||||
{
|
||||
const auto &cfg = m_agent->config();
|
||||
|
||||
const QString renderedContext = renderAgentContext();
|
||||
if (renderedContext.isEmpty())
|
||||
if (cfg.systemPrompt.isEmpty()) {
|
||||
m_systemPrompt->clearLayer(QStringLiteral("agent.system"));
|
||||
else
|
||||
m_systemPrompt->setLayer(QStringLiteral("agent.system"), renderedContext);
|
||||
} else {
|
||||
QString renderErr;
|
||||
const QString renderedContext = Templates::ContextRenderer::render(
|
||||
cfg.systemPrompt, m_contextBindings, &renderErr);
|
||||
if (!renderErr.isEmpty()) {
|
||||
m_lastError = makeError(
|
||||
ErrorCategory::Validation,
|
||||
QStringLiteral("Agent '%1' system_prompt render failed: %2")
|
||||
.arg(cfg.name, renderErr));
|
||||
qWarning("[QodeAssist] %s", qUtf8Printable(m_lastError.message));
|
||||
return {};
|
||||
}
|
||||
if (renderedContext.isEmpty())
|
||||
m_systemPrompt->clearLayer(QStringLiteral("agent.system"));
|
||||
else
|
||||
m_systemPrompt->setLayer(
|
||||
QStringLiteral("agent.system"), renderedContext, SystemPromptBuilder::kAgentPriority);
|
||||
}
|
||||
|
||||
const bool tools = toolsOverride.value_or(cfg.enableTools);
|
||||
const bool thinking = thinkingOverride.value_or(cfg.enableThinking);
|
||||
return dispatchContext(toLegacyContext(), tools, thinking);
|
||||
return dispatchContext(toLegacyContext(), tools);
|
||||
}
|
||||
|
||||
LLMQore::RequestID Session::dispatchContext(
|
||||
Templates::ContextData ctx, bool tools, bool thinking)
|
||||
LLMQore::RequestID Session::dispatchContext(Templates::ContextData ctx, bool tools)
|
||||
{
|
||||
m_lastError = {};
|
||||
|
||||
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))
|
||||
QString prepareErr;
|
||||
if (!provider->prepareRequest(payload, tmpl, ctx, tools, &prepareErr)) {
|
||||
m_lastError = makeError(ErrorCategory::Validation, prepareErr, prepareErr);
|
||||
return {};
|
||||
}
|
||||
|
||||
QString endpoint = cfg.endpoint;
|
||||
endpoint.replace(QStringLiteral("${MODEL}"), cfg.model);
|
||||
const auto id = provider->sendRequest(QUrl(provider->url()), payload, endpoint);
|
||||
if (id.isEmpty())
|
||||
if (id.isEmpty()) {
|
||||
m_lastError = makeError(
|
||||
ErrorCategory::Provider,
|
||||
QStringLiteral("Provider '%1' failed to start the request").arg(provider->name()));
|
||||
return {};
|
||||
}
|
||||
|
||||
m_inFlight = id;
|
||||
if (m_router)
|
||||
@@ -389,9 +408,11 @@ void Session::onRouterEvent(const ResponseEvent &ev)
|
||||
} else if (ev.kind() == ResponseEvent::Kind::Error) {
|
||||
const auto *err = ev.as<ResponseEvents::Error>();
|
||||
const QString msg = err ? err->message : QStringLiteral("unknown error");
|
||||
const ErrorCategory category = err ? err->category : ErrorCategory::Provider;
|
||||
m_lastError = makeError(category, msg, msg);
|
||||
const auto id = m_inFlight;
|
||||
m_inFlight.clear();
|
||||
emit failed(id, msg);
|
||||
emit failed(id, m_lastError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ConversationHistory.hpp"
|
||||
#include "ErrorInfo.hpp"
|
||||
#include "ResponseEvent.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
@@ -33,8 +34,6 @@ class Session : public QObject
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(Session)
|
||||
public:
|
||||
explicit Session(QObject *parent = nullptr);
|
||||
|
||||
Session(
|
||||
Agent *agent,
|
||||
ConversationHistory *externalHistory = nullptr,
|
||||
@@ -47,6 +46,7 @@ public:
|
||||
bool isValid() const noexcept;
|
||||
QString invalidReason() const;
|
||||
bool isInFlight() const noexcept;
|
||||
const ErrorInfo &lastError() const noexcept;
|
||||
|
||||
using ContentLoader = std::function<QString(const QString &storedPath)>;
|
||||
void setContentLoader(ContentLoader loader);
|
||||
@@ -60,12 +60,9 @@ public:
|
||||
|
||||
void setContextBindings(Templates::ContextRenderer::Bindings bindings);
|
||||
|
||||
QString renderAgentContext() const;
|
||||
|
||||
LLMQore::RequestID send(
|
||||
std::vector<std::unique_ptr<LLMQore::ContentBlock>> userBlocks,
|
||||
std::optional<bool> toolsOverride = std::nullopt,
|
||||
std::optional<bool> thinkingOverride = std::nullopt);
|
||||
std::optional<bool> toolsOverride = std::nullopt);
|
||||
|
||||
LLMQore::RequestID sendText(const QString &text);
|
||||
|
||||
@@ -78,16 +75,16 @@ signals:
|
||||
|
||||
void started(const LLMQore::RequestID &id);
|
||||
void finished(const LLMQore::RequestID &id, const QString &stopReason);
|
||||
void failed(const LLMQore::RequestID &id, const QString &error);
|
||||
void failed(const LLMQore::RequestID &id, const QodeAssist::ErrorInfo &error);
|
||||
void cancelled(const LLMQore::RequestID &id);
|
||||
|
||||
private slots:
|
||||
void onRouterEvent(const QodeAssist::ResponseEvent &ev);
|
||||
|
||||
private:
|
||||
LLMQore::RequestID dispatch(
|
||||
std::optional<bool> toolsOverride = std::nullopt,
|
||||
std::optional<bool> thinkingOverride = std::nullopt);
|
||||
LLMQore::RequestID dispatchContext(Templates::ContextData ctx, bool tools, bool thinking);
|
||||
LLMQore::RequestID dispatch(std::optional<bool> toolsOverride = std::nullopt);
|
||||
LLMQore::RequestID dispatchContext(Templates::ContextData ctx, bool tools);
|
||||
void teardownInFlight();
|
||||
Templates::ContextData toLegacyContext() const;
|
||||
|
||||
Agent *m_agent = nullptr; // child if non-null
|
||||
@@ -97,17 +94,16 @@ private:
|
||||
|
||||
LLMQore::RequestID m_inFlight;
|
||||
QString m_invalidReason;
|
||||
ErrorInfo m_lastError;
|
||||
|
||||
Templates::ContextRenderer::Bindings m_contextBindings;
|
||||
ContentLoader m_contentLoader;
|
||||
|
||||
public:
|
||||
static Templates::ContextData buildLegacyContext(
|
||||
const std::vector<Message> &history,
|
||||
const QString &systemPrompt,
|
||||
const ContentLoader &loader = ContentLoader{});
|
||||
|
||||
private:
|
||||
ContentLoader m_contentLoader;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
SessionManager::SessionManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
SessionManager::SessionManager(AgentFactory *agentFactory, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_agentFactory(agentFactory)
|
||||
@@ -21,14 +17,6 @@ SessionManager::SessionManager(AgentFactory *agentFactory, QObject *parent)
|
||||
|
||||
SessionManager::~SessionManager() = default;
|
||||
|
||||
Session *SessionManager::createSession()
|
||||
{
|
||||
auto *session = new Session(this);
|
||||
m_sessions.append(session);
|
||||
emit sessionCreated(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
Session *SessionManager::createSession(const QString &agentName, QString *errorOut)
|
||||
{
|
||||
return createSession(agentName, /*externalHistory=*/nullptr, errorOut);
|
||||
|
||||
@@ -22,14 +22,10 @@ class SessionManager : public QObject
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(SessionManager)
|
||||
public:
|
||||
explicit SessionManager(QObject *parent = nullptr);
|
||||
|
||||
SessionManager(AgentFactory *agentFactory, QObject *parent = nullptr);
|
||||
explicit SessionManager(AgentFactory *agentFactory, QObject *parent = nullptr);
|
||||
|
||||
~SessionManager() override;
|
||||
|
||||
Session *createSession();
|
||||
|
||||
Session *createSession(const QString &agentName, QString *errorOut = nullptr);
|
||||
|
||||
Session *createSession(
|
||||
|
||||
@@ -4,30 +4,34 @@
|
||||
|
||||
#include "SystemPromptBuilder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
SystemPromptBuilder::SystemPromptBuilder(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
void SystemPromptBuilder::setLayer(const QString &name, const QString &text)
|
||||
void SystemPromptBuilder::setLayer(const QString &name, const QString &text, int priority)
|
||||
{
|
||||
for (auto &pair : m_layers) {
|
||||
if (pair.first == name) {
|
||||
if (pair.second == text) return;
|
||||
pair.second = text;
|
||||
for (auto &layer : m_layers) {
|
||||
if (layer.name == name) {
|
||||
if (layer.text == text && layer.priority == priority)
|
||||
return;
|
||||
layer.text = text;
|
||||
layer.priority = priority;
|
||||
emit layersChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_layers.append({name, text});
|
||||
m_layers.append({name, text, priority});
|
||||
emit layersChanged();
|
||||
}
|
||||
|
||||
void SystemPromptBuilder::clearLayer(const QString &name)
|
||||
{
|
||||
for (auto it = m_layers.begin(); it != m_layers.end(); ++it) {
|
||||
if (it->first == name) {
|
||||
if (it->name == name) {
|
||||
m_layers.erase(it);
|
||||
emit layersChanged();
|
||||
return;
|
||||
@@ -44,8 +48,8 @@ void SystemPromptBuilder::clear()
|
||||
|
||||
QString SystemPromptBuilder::layer(const QString &name) const
|
||||
{
|
||||
for (const auto &pair : m_layers) {
|
||||
if (pair.first == name) return pair.second;
|
||||
for (const auto &l : m_layers) {
|
||||
if (l.name == name) return l.text;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -54,17 +58,22 @@ QStringList SystemPromptBuilder::layerNames() const
|
||||
{
|
||||
QStringList out;
|
||||
out.reserve(m_layers.size());
|
||||
for (const auto &pair : m_layers) out.append(pair.first);
|
||||
for (const auto &l : m_layers) out.append(l.name);
|
||||
return out;
|
||||
}
|
||||
|
||||
QString SystemPromptBuilder::compose(const QString &separator) const
|
||||
{
|
||||
QVector<Layer> ordered = m_layers;
|
||||
std::stable_sort(
|
||||
ordered.begin(), ordered.end(),
|
||||
[](const Layer &a, const Layer &b) { return a.priority < b.priority; });
|
||||
|
||||
QStringList parts;
|
||||
parts.reserve(m_layers.size());
|
||||
for (const auto &pair : m_layers) {
|
||||
if (!pair.second.isEmpty())
|
||||
parts.append(pair.second);
|
||||
parts.reserve(ordered.size());
|
||||
for (const auto &l : ordered) {
|
||||
if (!l.text.isEmpty())
|
||||
parts.append(l.text);
|
||||
}
|
||||
return parts.join(separator);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@ class SystemPromptBuilder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static constexpr int kAgentPriority = 0;
|
||||
static constexpr int kDefaultPriority = 100;
|
||||
|
||||
explicit SystemPromptBuilder(QObject *parent = nullptr);
|
||||
|
||||
void setLayer(const QString &name, const QString &text);
|
||||
void setLayer(const QString &name, const QString &text, int priority = kDefaultPriority);
|
||||
void clearLayer(const QString &name);
|
||||
void clear();
|
||||
|
||||
@@ -31,7 +34,14 @@ signals:
|
||||
void layersChanged();
|
||||
|
||||
private:
|
||||
QVector<QPair<QString, QString>> m_layers;
|
||||
struct Layer
|
||||
{
|
||||
QString name;
|
||||
QString text;
|
||||
int priority = kDefaultPriority;
|
||||
};
|
||||
|
||||
QVector<Layer> m_layers;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
@@ -55,6 +55,8 @@ Agent::Agent(AgentConfig config, Providers::Provider *providerOwned, QObject *pa
|
||||
return;
|
||||
}
|
||||
m_provider->setParent(this);
|
||||
m_provider->setPromptCaching(
|
||||
m_config.cachePrompt, m_config.cacheTtl == QLatin1StringView{"1h"});
|
||||
|
||||
QString tmplErr;
|
||||
m_promptTemplate = JsonPromptTemplate::fromConfig(m_config, &tmplErr);
|
||||
|
||||
@@ -39,6 +39,8 @@ struct AgentConfig
|
||||
|
||||
bool enableThinking = false;
|
||||
bool enableTools = false;
|
||||
bool cachePrompt = false;
|
||||
QString cacheTtl;
|
||||
|
||||
QJsonObject body;
|
||||
QString extendsName;
|
||||
|
||||
@@ -194,7 +194,7 @@ Agent *AgentFactory::createFromFile(
|
||||
{
|
||||
QString parseErr;
|
||||
QStringList warnings;
|
||||
auto cfgOpt = Agents::AgentLoader::parseFile(tomlPath, &parseErr, &warnings);
|
||||
auto cfgOpt = Agents::AgentLoader::parseFile(tomlPath, agentQrcPrefix(), &parseErr, &warnings);
|
||||
if (!cfgOpt) {
|
||||
if (errorOut) *errorOut = parseErr;
|
||||
return nullptr;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
@@ -123,6 +124,8 @@ AgentConfig configFromMerged(const QJsonObject &obj)
|
||||
cfg.systemPrompt = obj.value("system_prompt").toString();
|
||||
cfg.enableThinking = obj.value("enable_thinking").toBool(false);
|
||||
cfg.enableTools = obj.value("enable_tools").toBool(false);
|
||||
cfg.cachePrompt = obj.value("cache_prompt").toBool(false);
|
||||
cfg.cacheTtl = obj.value("cache_ttl").toString();
|
||||
cfg.tags = stringArray(obj.value("tags"));
|
||||
|
||||
const QJsonObject matchObj = obj.value("match").toObject();
|
||||
@@ -147,6 +150,34 @@ struct RawEntry
|
||||
|
||||
constexpr int kMaxExtendsDepth = 32;
|
||||
|
||||
void scanDir(
|
||||
const QString &dir,
|
||||
bool isUserLayer,
|
||||
QHash<QString, RawEntry> &raw,
|
||||
QStringList &errors)
|
||||
{
|
||||
if (dir.isEmpty()) return;
|
||||
QDir d(dir);
|
||||
if (!d.exists()) return;
|
||||
const QStringList files = d.entryList({"*.toml"}, QDir::Files);
|
||||
for (const QString &fname : files) {
|
||||
const QString fullPath = d.filePath(fname);
|
||||
QString err;
|
||||
auto objOpt = parseTomlFile(fullPath, &err);
|
||||
if (!objOpt) {
|
||||
errors.append(err);
|
||||
continue;
|
||||
}
|
||||
const QString name = objOpt->value("name").toString();
|
||||
if (name.isEmpty()) {
|
||||
errors.append(QStringLiteral("Agent at %1 has no 'name'").arg(fullPath));
|
||||
continue;
|
||||
}
|
||||
const bool overrides = isUserLayer && raw.contains(name);
|
||||
raw.insert(name, {*objOpt, fullPath, overrides});
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject resolveExtends(
|
||||
const QString &name,
|
||||
const QHash<QString, RawEntry> &raw,
|
||||
@@ -190,12 +221,47 @@ QJsonObject resolveExtends(
|
||||
} // namespace
|
||||
|
||||
std::optional<AgentConfig> AgentLoader::parseFile(
|
||||
const QString &path, QString *error, QStringList * /*warnings*/)
|
||||
const QString &path,
|
||||
const QString &qrcPrefix,
|
||||
QString *error,
|
||||
QStringList * /*warnings*/)
|
||||
{
|
||||
auto objOpt = parseTomlFile(path, error);
|
||||
if (!objOpt) return std::nullopt;
|
||||
AgentConfig cfg = configFromMerged(*objOpt);
|
||||
|
||||
const QString name = objOpt->value("name").toString();
|
||||
if (name.isEmpty()) {
|
||||
if (error) *error = QStringLiteral("Agent at %1 has no 'name'").arg(path);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QHash<QString, RawEntry> raw;
|
||||
QStringList scanErrors;
|
||||
scanDir(qrcPrefix, /*isUserLayer=*/false, raw, scanErrors);
|
||||
scanDir(QFileInfo(path).absolutePath(), /*isUserLayer=*/true, raw, scanErrors);
|
||||
raw.insert(name, {*objOpt, path, raw.contains(name)});
|
||||
|
||||
QSet<QString> visiting;
|
||||
QStringList resolveErrors;
|
||||
const QJsonObject merged = resolveExtends(name, raw, visiting, resolveErrors);
|
||||
if (!resolveErrors.isEmpty() || merged.isEmpty()) {
|
||||
if (error) {
|
||||
*error = resolveErrors.isEmpty()
|
||||
? QStringLiteral("Agent '%1' resolved to an empty config").arg(name)
|
||||
: resolveErrors.join(QStringLiteral("; "));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AgentConfig cfg = configFromMerged(merged);
|
||||
cfg.sourcePath = path;
|
||||
if (cfg.abstract) {
|
||||
if (error) {
|
||||
*error = QStringLiteral("Agent '%1' is abstract — extend it instead of "
|
||||
"loading it directly").arg(name);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@@ -204,31 +270,8 @@ AgentLoader::LoadResult AgentLoader::load(const QString &qrcPrefix, const QStrin
|
||||
LoadResult result;
|
||||
QHash<QString, RawEntry> raw;
|
||||
|
||||
auto scan = [&](const QString &dir, bool isUserLayer) {
|
||||
if (dir.isEmpty()) return;
|
||||
QDir d(dir);
|
||||
if (!d.exists()) return;
|
||||
const QStringList files = d.entryList({"*.toml"}, QDir::Files);
|
||||
for (const QString &fname : files) {
|
||||
const QString fullPath = d.filePath(fname);
|
||||
QString err;
|
||||
auto objOpt = parseTomlFile(fullPath, &err);
|
||||
if (!objOpt) {
|
||||
result.errors.append(err);
|
||||
continue;
|
||||
}
|
||||
const QString name = objOpt->value("name").toString();
|
||||
if (name.isEmpty()) {
|
||||
result.errors.append(QStringLiteral("Agent at %1 has no 'name'").arg(fullPath));
|
||||
continue;
|
||||
}
|
||||
const bool overrides = isUserLayer && raw.contains(name);
|
||||
raw.insert(name, {*objOpt, fullPath, overrides});
|
||||
}
|
||||
};
|
||||
|
||||
scan(qrcPrefix, /*isUserLayer=*/false);
|
||||
scan(userDir, /*isUserLayer=*/true);
|
||||
scanDir(qrcPrefix, /*isUserLayer=*/false, raw, result.errors);
|
||||
scanDir(userDir, /*isUserLayer=*/true, raw, result.errors);
|
||||
|
||||
for (auto it = raw.constBegin(); it != raw.constEnd(); ++it) {
|
||||
const QString &name = it.key();
|
||||
|
||||
@@ -25,7 +25,10 @@ public:
|
||||
static LoadResult load(const QString &qrcPrefix, const QString &userDir);
|
||||
|
||||
static std::optional<AgentConfig> parseFile(
|
||||
const QString &path, QString *error, QStringList *warnings = nullptr);
|
||||
const QString &path,
|
||||
const QString &qrcPrefix,
|
||||
QString *error,
|
||||
QStringList *warnings = nullptr);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Agents
|
||||
|
||||
@@ -7,6 +7,7 @@ abstract = true
|
||||
provider_instance = "Claude"
|
||||
endpoint = "/v1/messages"
|
||||
enable_tools = true
|
||||
cache_prompt = true
|
||||
tags = ["chat", "claude", "anthropic", "cloud"]
|
||||
|
||||
system_prompt = """{{ agent_role() }}"""
|
||||
|
||||
@@ -3,8 +3,17 @@
|
||||
"role": {{ tojson(msg.role) }},
|
||||
"content": [
|
||||
{% for b in msg.content_blocks %}
|
||||
{% if b.type == "image" %}{% include "partials/anthropic_image.jinja" %}
|
||||
{% else %}{{ tojson(b) }},
|
||||
{% if b.type == "text" %}
|
||||
{ "type": "text", "text": {{ tojson(b.text) }} },
|
||||
{% else if b.type == "thinking" %}
|
||||
{ "type": "thinking", "thinking": {{ tojson(b.thinking) }}, "signature": {{ tojson(b.signature) }} },
|
||||
{% else if b.type == "redacted_thinking" %}
|
||||
{ "type": "redacted_thinking", "data": {{ tojson(b.data) }} },
|
||||
{% else if b.type == "tool_use" %}
|
||||
{ "type": "tool_use", "id": {{ tojson(b.id) }}, "name": {{ tojson(b.name) }}, "input": {{ tojson(b.input) }} },
|
||||
{% else if b.type == "tool_result" %}
|
||||
{ "type": "tool_result", "tool_use_id": {{ tojson(b.tool_use_id) }}, "content": {{ tojson(b.content) }} },
|
||||
{% else if b.type == "image" %}{% include "partials/anthropic_image.jinja" %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
@@ -2,15 +2,36 @@
|
||||
{ "role": "system", "content": {{ tojson(ctx.system_prompt) }} },
|
||||
{% endif %}
|
||||
{% for msg in ctx.history %}
|
||||
{
|
||||
"role": {{ tojson(msg.role) }},
|
||||
"content": {{ tojson(msg.content) }}
|
||||
{% if existsIn(msg, "images") %}
|
||||
, "images": [
|
||||
{% for img in msg.images %}
|
||||
{{ tojson(img.data) }},
|
||||
{% set tcalls = filter_by_type(msg.content_blocks, "tool_use") %}
|
||||
{% set tresults = filter_by_type(msg.content_blocks, "tool_result") %}
|
||||
{% if length(tresults) > 0 %}
|
||||
{% for b in tresults %}
|
||||
{
|
||||
"role": "tool",
|
||||
"content": {{ tojson(b.content) }}
|
||||
{% if b.name != "" %}
|
||||
, "tool_name": {{ tojson(b.name) }}
|
||||
{% endif %}
|
||||
},
|
||||
{% endfor %}
|
||||
]
|
||||
{% else %}
|
||||
{
|
||||
"role": {{ tojson(msg.role) }},
|
||||
"content": {{ tojson(msg.content) }}
|
||||
{% if length(tcalls) > 0 %}
|
||||
, "tool_calls": [
|
||||
{% for b in tcalls %}
|
||||
{ "type": "function", "function": { "name": {{ tojson(b.name) }}, "arguments": {{ tojson(b.input) }} } },
|
||||
{% endfor %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if existsIn(msg, "images") %}
|
||||
, "images": [
|
||||
{% for img in msg.images %}
|
||||
{{ tojson(img.data) }},
|
||||
{% endfor %}
|
||||
]
|
||||
{% endif %}
|
||||
},
|
||||
{% endif %}
|
||||
},
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% set tcalls = filter_by_type(msg.content_blocks, "tool_use") %}
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": {{ tojson(msg.content) }}
|
||||
"content": {% if msg.content != "" %}{{ tojson(msg.content) }}{% else %}null{% endif %}
|
||||
{% if length(tcalls) > 0 %}
|
||||
, "tool_calls": [
|
||||
{% for b in tcalls %}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
[
|
||||
{ "type": "text", "text": {{ tojson(msg.content) }} }
|
||||
{% if msg.content != "" %}
|
||||
{ "type": "text", "text": {{ tojson(msg.content) }} },
|
||||
{% endif %}
|
||||
{% for img in msg.images %}
|
||||
,
|
||||
{% if img.is_url %}
|
||||
{ "type": "image_url", "image_url": { "url": {{ tojson(img.data) }} } }
|
||||
{ "type": "image_url", "image_url": { "url": {{ tojson(img.data) }} } },
|
||||
{% else %}
|
||||
{ "type": "image_url", "image_url": { "url": "data:{{ img.media_type }};base64,{{ img.data }}" } }
|
||||
{ "type": "image_url", "image_url": { "url": "data:{{ img.media_type }};base64,{{ img.data }}" } },
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@ add_library(Providers STATIC
|
||||
Provider.hpp Provider.cpp
|
||||
ProviderFactory.hpp ProviderFactory.cpp
|
||||
GenericProvider.hpp GenericProvider.cpp
|
||||
ClaudeCacheControl.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(Providers
|
||||
|
||||
90
sources/providers/ClaudeCacheControl.hpp
Normal file
90
sources/providers/ClaudeCacheControl.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Providers::ClaudeCacheControl {
|
||||
|
||||
inline QJsonObject buildBreakpoint(bool extendedTtl)
|
||||
{
|
||||
QJsonObject cacheControl{{"type", "ephemeral"}};
|
||||
if (extendedTtl)
|
||||
cacheControl["ttl"] = "1h";
|
||||
return cacheControl;
|
||||
}
|
||||
|
||||
inline void markLastBlock(QJsonArray &blocks, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (blocks.isEmpty())
|
||||
return;
|
||||
QJsonObject last = blocks.last().toObject();
|
||||
last["cache_control"] = cacheControl;
|
||||
blocks.replace(blocks.size() - 1, last);
|
||||
}
|
||||
|
||||
inline void applyToSystem(QJsonObject &request, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (!request.contains("system"))
|
||||
return;
|
||||
|
||||
const QJsonValue sys = request.value("system");
|
||||
if (sys.isString()) {
|
||||
const QString text = sys.toString();
|
||||
if (!text.isEmpty()) {
|
||||
request["system"] = QJsonArray{QJsonObject{
|
||||
{"type", "text"}, {"text", text}, {"cache_control", cacheControl}}};
|
||||
}
|
||||
} else if (sys.isArray()) {
|
||||
QJsonArray blocks = sys.toArray();
|
||||
markLastBlock(blocks, cacheControl);
|
||||
request["system"] = blocks;
|
||||
}
|
||||
}
|
||||
|
||||
inline void applyToTools(QJsonObject &request, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (!request.contains("tools"))
|
||||
return;
|
||||
QJsonArray tools = request.value("tools").toArray();
|
||||
markLastBlock(tools, cacheControl);
|
||||
request["tools"] = tools;
|
||||
}
|
||||
|
||||
inline void applyToHistory(QJsonObject &request, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (!request.contains("messages"))
|
||||
return;
|
||||
QJsonArray messages = request.value("messages").toArray();
|
||||
if (messages.size() < 2)
|
||||
return;
|
||||
|
||||
const int idx = messages.size() - 2;
|
||||
QJsonObject msg = messages[idx].toObject();
|
||||
const QJsonValue content = msg.value("content");
|
||||
if (content.isString()) {
|
||||
msg["content"] = QJsonArray{QJsonObject{
|
||||
{"type", "text"}, {"text", content.toString()}, {"cache_control", cacheControl}}};
|
||||
} else if (content.isArray()) {
|
||||
QJsonArray blocks = content.toArray();
|
||||
markLastBlock(blocks, cacheControl);
|
||||
msg["content"] = blocks;
|
||||
}
|
||||
messages.replace(idx, msg);
|
||||
request["messages"] = messages;
|
||||
}
|
||||
|
||||
inline void apply(QJsonObject &request, bool extendedTtl)
|
||||
{
|
||||
const QJsonObject cacheControl = buildBreakpoint(extendedTtl);
|
||||
applyToSystem(request, cacheControl);
|
||||
applyToTools(request, cacheControl);
|
||||
applyToHistory(request, cacheControl);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers::ClaudeCacheControl
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include "Provider.hpp"
|
||||
|
||||
#include "ClaudeCacheControl.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
#include <LLMQore/BaseClient.hpp>
|
||||
#include <LLMQore/ClaudeClient.hpp>
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include <QJsonArray>
|
||||
@@ -25,24 +27,27 @@ bool Provider::prepareRequest(
|
||||
PromptTemplate *prompt,
|
||||
const ContextData &context,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
QString *errorOut)
|
||||
{
|
||||
if (!prompt) {
|
||||
LOG_MESSAGE(QString("Provider '%1': null template").arg(name()));
|
||||
const auto fail = [errorOut](const QString &message) {
|
||||
LOG_MESSAGE(message);
|
||||
if (errorOut)
|
||||
*errorOut = message;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!prompt)
|
||||
return fail(QString("Provider '%1': null template").arg(name()));
|
||||
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template '%1' doesn't support provider '%2'")
|
||||
return fail(QString("Template '%1' doesn't support provider '%2'")
|
||||
.arg(prompt->name(), name()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!prompt->buildFullRequest(request, context, isThinkingEnabled)) {
|
||||
LOG_MESSAGE(
|
||||
QString("Provider '%1': template '%2' failed to build request")
|
||||
if (!prompt->buildFullRequest(request, context)) {
|
||||
return fail(
|
||||
QString("Provider '%1': template '%2' failed to build request (see log)")
|
||||
.arg(name(), prompt->name()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
@@ -51,9 +56,21 @@ bool Provider::prepareRequest(
|
||||
request["tools"] = toolsDefinitions;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_promptCachingEnabled)
|
||||
ClaudeCacheControl::apply(request, m_promptCachingExtendedTtl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Provider::setPromptCaching(bool enabled, bool extendedTtl)
|
||||
{
|
||||
m_promptCachingEnabled = enabled;
|
||||
m_promptCachingExtendedTtl = enabled && extendedTtl;
|
||||
if (auto *claude = qobject_cast<::LLMQore::ClaudeClient *>(client()))
|
||||
claude->setUseExtendedCacheTTL(m_promptCachingExtendedTtl);
|
||||
}
|
||||
|
||||
RequestID Provider::sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
PromptTemplate *prompt,
|
||||
const ContextData &context,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled);
|
||||
QString *errorOut = nullptr);
|
||||
virtual QFuture<QList<QString>> getInstalledModels(const QString &url) = 0;
|
||||
virtual ProviderID providerID() const = 0;
|
||||
virtual ProviderCapabilities capabilities() const { return {}; }
|
||||
@@ -73,9 +73,13 @@ public:
|
||||
void cancelRequest(const RequestID &requestId);
|
||||
::LLMQore::ToolsManager *toolsManager() const;
|
||||
|
||||
void setPromptCaching(bool enabled, bool extendedTtl);
|
||||
|
||||
private:
|
||||
QString m_url;
|
||||
QString m_apiKey;
|
||||
bool m_promptCachingEnabled = false;
|
||||
bool m_promptCachingExtendedTtl = false;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -310,6 +310,15 @@ ContextData makeValidationContext()
|
||||
Message asst;
|
||||
asst.role = QStringLiteral("assistant");
|
||||
{
|
||||
ContentBlockEntry th;
|
||||
th.kind = ContentBlockEntry::Kind::Thinking;
|
||||
th.thinking = QStringLiteral("reasoning");
|
||||
th.signature = QStringLiteral("sig");
|
||||
asst.blocks.append(th);
|
||||
ContentBlockEntry rth;
|
||||
rth.kind = ContentBlockEntry::Kind::RedactedThinking;
|
||||
rth.signature = QStringLiteral("sig");
|
||||
asst.blocks.append(rth);
|
||||
ContentBlockEntry t;
|
||||
t.kind = ContentBlockEntry::Kind::Text;
|
||||
t.text = QStringLiteral("hi");
|
||||
@@ -516,9 +525,7 @@ void JsonPromptTemplate::prepareRequest(QJsonObject &request, const ContextData
|
||||
}
|
||||
|
||||
bool JsonPromptTemplate::buildFullRequest(
|
||||
QJsonObject &request,
|
||||
const ContextData &context,
|
||||
bool /*thinkingEnabled*/) const
|
||||
QJsonObject &request, const ContextData &context) const
|
||||
{
|
||||
return mergeRenderedBody(request, renderBody(context));
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ public:
|
||||
void prepareRequest(QJsonObject &request, const ContextData &context) const override;
|
||||
|
||||
[[nodiscard]] bool buildFullRequest(
|
||||
QJsonObject &request,
|
||||
const ContextData &context,
|
||||
bool thinkingEnabled = false) const override;
|
||||
QJsonObject &request, const ContextData &context) const override;
|
||||
|
||||
private:
|
||||
JsonPromptTemplate() = default;
|
||||
|
||||
@@ -40,9 +40,7 @@ public:
|
||||
virtual bool isSupportModel(const QString & /*modelName*/) const { return true; }
|
||||
|
||||
[[nodiscard]] virtual bool buildFullRequest(
|
||||
QJsonObject &request,
|
||||
const ContextData &context,
|
||||
bool /*thinkingEnabled*/ = false) const
|
||||
QJsonObject &request, const ContextData &context) const
|
||||
{
|
||||
prepareRequest(request, context);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user