From 98a618cf878b13b190363faf972ad81321db0e36 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Tue, 2 Jun 2026 01:10:29 +0200 Subject: [PATCH] refactor: Add agents for providers --- ChatView/ChatCompressor.cpp | 3 +- ChatView/ChatRootView.cpp | 48 ++++++-------- ChatView/ChatRootView.hpp | 16 ++--- ChatView/ClientInterface.cpp | 8 +-- ChatView/ClientInterface.hpp | 4 +- ChatView/qml/RootItem.qml | 13 ---- ChatView/qml/controls/TopBar.qml | 58 ----------------- sources/Session/Session.cpp | 19 ++++-- sources/Session/Session.hpp | 7 ++- sources/agents/agents.qrc | 2 + sources/agents/claude_sonnet_chat.toml | 77 +++++++++++++++++++++++ sources/agents/google_gemini_chat.toml | 79 ++++++++++++++++++++++++ sources/providers/GenericProvider.cpp | 16 +++++ sources/providers/GenericProvider.hpp | 3 + sources/templates/JsonPromptTemplate.cpp | 12 ++++ 15 files changed, 238 insertions(+), 127 deletions(-) create mode 100644 sources/agents/claude_sonnet_chat.toml create mode 100644 sources/agents/google_gemini_chat.toml diff --git a/ChatView/ChatCompressor.cpp b/ChatView/ChatCompressor.cpp index f062a3d..a5b1a9d 100644 --- a/ChatView/ChatCompressor.cpp +++ b/ChatView/ChatCompressor.cpp @@ -125,7 +125,8 @@ void ChatCompressor::startCompression(const QString &chatFilePath, ChatModel *ch std::vector> blocks; blocks.push_back(std::make_unique(buildCompressionPrompt())); - m_currentRequestId = session->send(std::move(blocks), /*toolsOverride=*/false); + m_currentRequestId = session->send( + std::move(blocks), /*toolsOverride=*/false, /*thinkingOverride=*/false); if (m_currentRequestId.isEmpty()) { handleCompressionError(tr("Failed to start compression request")); return; diff --git a/ChatView/ChatRootView.cpp b/ChatView/ChatRootView.cpp index c86ad12..177ae0f 100644 --- a/ChatView/ChatRootView.cpp +++ b/ChatView/ChatRootView.cpp @@ -167,6 +167,16 @@ ChatRootView::ChatRootView(QQuickItem *parent) &ChatAgentController::currentAgentChanged, this, &ChatRootView::isThinkingSupportChanged); + connect( + m_agentController, + &ChatAgentController::currentAgentChanged, + this, + &ChatRootView::useToolsChanged); + connect( + m_agentController, + &ChatAgentController::currentAgentChanged, + this, + &ChatRootView::useThinkingChanged); auto editors = Core::EditorManager::instance(); @@ -292,7 +302,7 @@ ChatRootView::ChatRootView(QQuickItem *parent) if (m_pendingSend.active) { PendingSend p = m_pendingSend; m_pendingSend = {}; - dispatchSend(p.message, p.attachments, p.linkedFiles, p.useTools, p.useThinking); + dispatchSend(p.message, p.attachments, p.linkedFiles); } }); @@ -305,7 +315,7 @@ ChatRootView::ChatRootView(QQuickItem *parent) if (m_pendingSend.active) { PendingSend p = m_pendingSend; m_pendingSend = {}; - dispatchSend(p.message, p.attachments, p.linkedFiles, p.useTools, p.useThinking); + dispatchSend(p.message, p.attachments, p.linkedFiles); } }); } @@ -426,21 +436,17 @@ void ChatRootView::sendMessage(const QString &message) { const QStringList attachments = m_attachmentFiles; const QStringList linkedFiles = m_linkedFiles; - const bool tools = useTools(); - const bool thinking = useThinking(); - if (deferSendForAutoCompress(message, attachments, linkedFiles, tools, thinking)) + if (deferSendForAutoCompress(message, attachments, linkedFiles)) return; - dispatchSend(message, attachments, linkedFiles, tools, thinking); + dispatchSend(message, attachments, linkedFiles); } bool ChatRootView::deferSendForAutoCompress( const QString &message, const QStringList &attachments, - const QStringList &linkedFiles, - bool useToolsArg, - bool useThinkingArg) + const QStringList &linkedFiles) { auto &settings = Settings::chatAssistantSettings(); if (!settings.autoCompress()) @@ -466,7 +472,7 @@ bool ChatRootView::deferSendForAutoCompress( .arg(inputTokens) .arg(threshold)); - m_pendingSend = {message, attachments, linkedFiles, useToolsArg, useThinkingArg, true}; + m_pendingSend = {message, attachments, linkedFiles, true}; compressCurrentChat(); return true; } @@ -474,9 +480,7 @@ bool ChatRootView::deferSendForAutoCompress( void ChatRootView::dispatchSend( const QString &message, const QStringList &attachments, - const QStringList &linkedFiles, - bool useToolsArg, - bool useThinkingArg) + const QStringList &linkedFiles) { if (m_recentFilePath.isEmpty()) { QString filePath = getAutosaveFilePath(message, attachments); @@ -497,7 +501,7 @@ void ChatRootView::dispatchSend( m_clientInterface->setSkillsManager(skillsManager()); m_clientInterface->setSessionManager(sessionManager()); m_clientInterface->setActiveAgent(currentChatAgent()); - m_clientInterface->sendMessage(message, attachments, linkedFiles, useToolsArg, useThinkingArg); + m_clientInterface->sendMessage(message, attachments, linkedFiles); m_fileManager->clearIntermediateStorage(); clearAttachmentFiles(); @@ -1112,24 +1116,12 @@ QString ChatRootView::lastErrorMessage() const bool ChatRootView::useTools() const { - return Settings::chatAssistantSettings().enableChatTools(); -} - -void ChatRootView::setUseTools(bool enabled) -{ - Settings::chatAssistantSettings().enableChatTools.setValue(enabled); - Settings::chatAssistantSettings().writeSettings(); + return m_agentController->currentSupportsTools(); } bool ChatRootView::useThinking() const { - return Settings::chatAssistantSettings().enableThinkingMode(); -} - -void ChatRootView::setUseThinking(bool enabled) -{ - Settings::chatAssistantSettings().enableThinkingMode.setValue(enabled); - Settings::chatAssistantSettings().writeSettings(); + return m_agentController->currentSupportsThinking(); } void ChatRootView::applyFileEdit(const QString &editId) diff --git a/ChatView/ChatRootView.hpp b/ChatView/ChatRootView.hpp index ddd0299..079cbae 100644 --- a/ChatView/ChatRootView.hpp +++ b/ChatView/ChatRootView.hpp @@ -48,8 +48,8 @@ class ChatRootView : public QQuickItem Q_PROPERTY(bool isRequestInProgress READ isRequestInProgress NOTIFY isRequestInProgressChanged FINAL) Q_PROPERTY(QString lastErrorMessage READ lastErrorMessage NOTIFY lastErrorMessageChanged FINAL) Q_PROPERTY(QString lastInfoMessage READ lastInfoMessage NOTIFY lastInfoMessageChanged FINAL) - Q_PROPERTY(bool useTools READ useTools WRITE setUseTools NOTIFY useToolsChanged FINAL) - Q_PROPERTY(bool useThinking READ useThinking WRITE setUseThinking NOTIFY useThinkingChanged FINAL) + Q_PROPERTY(bool useTools READ useTools NOTIFY useToolsChanged FINAL) + Q_PROPERTY(bool useThinking READ useThinking NOTIFY useThinkingChanged FINAL) Q_PROPERTY(QString sendShortcutText READ sendShortcutText NOTIFY sendShortcutTextChanged FINAL) Q_PROPERTY(int currentMessageTotalEdits READ currentMessageTotalEdits NOTIFY currentMessageEditsStatsChanged FINAL) @@ -136,9 +136,7 @@ public: Q_INVOKABLE QVariantList searchSkills(const QString &query) const; bool useTools() const; - void setUseTools(bool enabled); bool useThinking() const; - void setUseThinking(bool enabled); Q_INVOKABLE void applyFileEdit(const QString &editId); Q_INVOKABLE void rejectFileEdit(const QString &editId); @@ -229,15 +227,11 @@ private: bool deferSendForAutoCompress( const QString &message, const QStringList &attachments, - const QStringList &linkedFiles, - bool useTools, - bool useThinking); + const QStringList &linkedFiles); void dispatchSend( const QString &message, const QStringList &attachments, - const QStringList &linkedFiles, - bool useTools, - bool useThinking); + const QStringList &linkedFiles); bool hasImageAttachments(const QStringList &attachments) const; SessionFileRegistry *sessionFileRegistry() const; @@ -256,8 +250,6 @@ private: QString message; QStringList attachments; QStringList linkedFiles; - bool useTools = false; - bool useThinking = false; bool active = false; }; PendingSend m_pendingSend; diff --git a/ChatView/ClientInterface.cpp b/ChatView/ClientInterface.cpp index 1cac885..568b980 100644 --- a/ChatView/ClientInterface.cpp +++ b/ChatView/ClientInterface.cpp @@ -78,12 +78,8 @@ void ClientInterface::setActiveAgent(const QString &agentName) void ClientInterface::sendMessage( const QString &message, const QList &attachments, - const QList &linkedFiles, - bool useTools, - bool useThinking) + const QList &linkedFiles) { - Q_UNUSED(useThinking) - if (message.trimmed().isEmpty() && attachments.isEmpty()) { LOG_MESSAGE("Ignoring empty chat message"); return; @@ -256,7 +252,7 @@ void ClientInterface::sendMessage( } } - const LLMQore::RequestID requestId = session->send(std::move(blocks), useTools); + const LLMQore::RequestID requestId = session->send(std::move(blocks)); if (requestId.isEmpty()) { const QString error = QStringLiteral("Failed to start chat request for agent: %1") .arg(m_activeAgent); diff --git a/ChatView/ClientInterface.hpp b/ChatView/ClientInterface.hpp index 7dc6221..89592e9 100644 --- a/ChatView/ClientInterface.hpp +++ b/ChatView/ClientInterface.hpp @@ -41,9 +41,7 @@ public: void sendMessage( const QString &message, const QList &attachments = {}, - const QList &linkedFiles = {}, - bool useTools = false, - bool useThinking = false); + const QList &linkedFiles = {}); void clearMessages(); void cancelRequest(); diff --git a/ChatView/qml/RootItem.qml b/ChatView/qml/RootItem.qml index e0e5f3b..4bf5d5d 100644 --- a/ChatView/qml/RootItem.qml +++ b/ChatView/qml/RootItem.qml @@ -138,19 +138,6 @@ ChatRootView { relocateTooltip.text: (typeof _chatview !== 'undefined') ? qsTr("Move this chat to an editor tab") : qsTr("Move this chat to a separate window") - toolsButton { - checked: root.useTools - onCheckedChanged: { - root.useTools = toolsButton.checked - } - } - thinkingMode { - checked: root.useThinking - enabled: root.isThinkingSupport - onCheckedChanged: { - root.useThinking = thinkingMode.checked - } - } settingsButton.onClicked: root.openSettings() agentSelector { model: root.availableChatAgents diff --git a/ChatView/qml/controls/TopBar.qml b/ChatView/qml/controls/TopBar.qml index 27a1bab..1b64d7c 100644 --- a/ChatView/qml/controls/TopBar.qml +++ b/ChatView/qml/controls/TopBar.qml @@ -23,8 +23,6 @@ Rectangle { property alias pinButton: pinButtonId property alias relocateButton: relocateButtonId property alias contextButton: contextButtonId - property alias toolsButton: toolsButtonId - property alias thinkingMode: thinkingModeId property alias settingsButton: settingsButtonId property alias agentSelector: agentSelectorId property alias relocateTooltip: relocateTooltipId @@ -151,62 +149,6 @@ Rectangle { Row { spacing: 10 - QoAButton { - id: toolsButtonId - - anchors.verticalCenter: parent.verticalCenter - - checkable: true - opacity: enabled ? 1.0 : 0.2 - - icon { - source: checked ? "qrc:/qt/qml/ChatView/icons/tools-icon-on.svg" - : "qrc:/qt/qml/ChatView/icons/tools-icon-off.svg" - color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF" - height: 15 - width: 15 - } - - QoAToolTip { - visible: toolsButtonId.hovered - delay: 250 - text: { - if (!toolsButtonId.enabled) { - return qsTr("Tools are disabled in General Settings") - } - return toolsButtonId.checked - ? qsTr("Tools enabled: AI can use tools to read files, search project, and build code") - : qsTr("Tools disabled: Simple conversation without tool access") - } - } - } - - QoAButton { - id: thinkingModeId - - anchors.verticalCenter: parent.verticalCenter - - checkable: true - opacity: enabled ? 1.0 : 0.2 - - icon { - source: checked ? "qrc:/qt/qml/ChatView/icons/thinking-icon-on.svg" - : "qrc:/qt/qml/ChatView/icons/thinking-icon-off.svg" - color: palette.window.hslLightness > 0.5 ? "#000000" : "#FFFFFF" - height: 15 - width: 15 - } - - QoAToolTip { - visible: thinkingModeId.hovered - delay: 250 - text: thinkingModeId.enabled - ? (thinkingModeId.checked ? qsTr("Thinking Mode enabled (Check model list support it)") - : qsTr("Thinking Mode disabled")) - : qsTr("Thinking Mode is not available for this provider") - } - } - QoAButton { id: settingsButtonId diff --git a/sources/Session/Session.cpp b/sources/Session/Session.cpp index c147aab..a0354dc 100644 --- a/sources/Session/Session.cpp +++ b/sources/Session/Session.cpp @@ -153,7 +153,8 @@ LLMQore::RequestID Session::sendText(const QString &text) LLMQore::RequestID Session::send( std::vector> userBlocks, - std::optional toolsOverride) + std::optional toolsOverride, + std::optional thinkingOverride) { if (!isValid() || userBlocks.empty()) return {}; @@ -168,7 +169,7 @@ LLMQore::RequestID Session::send( msg.appendBlock(std::move(b)); m_history->append(std::move(msg)); - return dispatch(toolsOverride); + return dispatch(toolsOverride, thinkingOverride); } void Session::cancel() @@ -221,7 +222,8 @@ LLMQore::RequestID Session::sendCompletion(Templates::ContextData ctx) return id; } -LLMQore::RequestID Session::dispatch(std::optional toolsOverride) +LLMQore::RequestID Session::dispatch( + std::optional toolsOverride, std::optional thinkingOverride) { auto *provider = m_agent->provider(); auto *tmpl = m_agent->promptTemplate(); @@ -237,7 +239,8 @@ LLMQore::RequestID Session::dispatch(std::optional toolsOverride) QJsonObject payload{{QStringLiteral("model"), cfg.model}}; const bool tools = toolsOverride.value_or(cfg.enableTools); - if (!provider->prepareRequest(payload, tmpl, ctx, tools, cfg.enableThinking)) + const bool thinking = thinkingOverride.value_or(cfg.enableThinking); + if (!provider->prepareRequest(payload, tmpl, ctx, tools, thinking)) return {}; const auto id = provider->sendRequest(QUrl(provider->url()), payload, cfg.endpoint); @@ -285,6 +288,9 @@ Templates::ContextData Session::buildLegacyContext( QVector hist; for (const auto &m : history) { + if (m.role() == Message::Role::System) + continue; + QVector blockEntries; for (const auto &blockPtr : m.blocks()) { @@ -329,12 +335,17 @@ Templates::ContextData Session::buildLegacyContext( .arg(sa->fileName(), text); blockEntries.append(std::move(e)); } else if (auto *th = dynamic_cast(block)) { + // Claude rejects thinking blocks replayed without a signature. + if (th->signature().isEmpty()) + continue; ContentBlockEntry e; e.kind = ContentBlockEntry::Kind::Thinking; e.thinking = th->thinking(); e.signature = th->signature(); blockEntries.append(std::move(e)); } else if (auto *rth = dynamic_cast(block)) { + if (rth->signature().isEmpty()) + continue; ContentBlockEntry e; e.kind = ContentBlockEntry::Kind::RedactedThinking; e.signature = rth->signature(); diff --git a/sources/Session/Session.hpp b/sources/Session/Session.hpp index 7f484b1..d3e7e1a 100644 --- a/sources/Session/Session.hpp +++ b/sources/Session/Session.hpp @@ -64,7 +64,8 @@ public: LLMQore::RequestID send( std::vector> userBlocks, - std::optional toolsOverride = std::nullopt); + std::optional toolsOverride = std::nullopt, + std::optional thinkingOverride = std::nullopt); LLMQore::RequestID sendText(const QString &text); @@ -83,7 +84,9 @@ private slots: void onRouterEvent(const QodeAssist::ResponseEvent &ev); private: - LLMQore::RequestID dispatch(std::optional toolsOverride = std::nullopt); + LLMQore::RequestID dispatch( + std::optional toolsOverride = std::nullopt, + std::optional thinkingOverride = std::nullopt); Templates::ContextData toLegacyContext() const; Agent *m_agent = nullptr; // child if non-null diff --git a/sources/agents/agents.qrc b/sources/agents/agents.qrc index 8bf4c2d..5501897 100644 --- a/sources/agents/agents.qrc +++ b/sources/agents/agents.qrc @@ -5,5 +5,7 @@ ollama_gemma4_e4b_chat.toml ollama_codellama_7b_code_fim.toml ollama_codellama_13b_qml_fim.toml + claude_sonnet_chat.toml + google_gemini_chat.toml diff --git a/sources/agents/claude_sonnet_chat.toml b/sources/agents/claude_sonnet_chat.toml new file mode 100644 index 0000000..f332778 --- /dev/null +++ b/sources/agents/claude_sonnet_chat.toml @@ -0,0 +1,77 @@ +schema_version = 1 + +name = "Claude Sonnet Chat" +description = "Anthropic Claude (Messages API) — coding chat assistant via the hosted Claude provider." + +provider_instance = "Claude" +endpoint = "/v1/messages" + +model = "claude-sonnet-4-6" + +role = """ +You are a helpful coding assistant integrated into Qt Creator. +Answer concisely. When the user shares code, prefer concrete diffs or +minimal patches over rewriting whole files. Use markdown code blocks +with language tags so the IDE can render them. +""" + +enable_thinking = true +enable_tools = true + +tags = ["chat", "claude", "anthropic", "cloud"] + +context = """ +{%- set readme = read_file("${PROJECT_DIR}/README.md") -%} +{%- if length(readme) > 0 %} +## Project README.md +{{ readme }} +{%- endif %} +""" + +[template] +message_format = """ +{ + {%- if existsIn(ctx, "system_prompt") %} + "system": {{ tojson(ctx.system_prompt) }}, + {%- endif %} + "messages": [ + {%- for msg in ctx.history %} + { + "role": {{ tojson(msg.role) }}, + "content": [ + {%- for b in msg.content_blocks %} + {%- if b.type == "image" %} + { + "type": "image", + "source": { + {%- if b.is_url %} + "type": "url", + "url": {{ tojson(b.data) }} + {%- else %} + "type": "base64", + "media_type": {{ tojson(b.media_type) }}, + "data": {{ tojson(b.data) }} + {%- endif %} + } + }{% if not loop.is_last %},{% endif %} + {%- else %} + {{ tojson(b) }}{% if not loop.is_last %},{% endif %} + {%- endif %} + {%- endfor %} + ] + }{% if not loop.is_last %},{% endif %} + {%- endfor %} + ] +} +""" + +[template.sampling] +max_tokens = 8192 +temperature = 1 + +[template.thinking.overrides] +temperature = 1 + +[template.thinking.request_block.thinking] +type = "enabled" +budget_tokens = 4096 diff --git a/sources/agents/google_gemini_chat.toml b/sources/agents/google_gemini_chat.toml new file mode 100644 index 0000000..16e6289 --- /dev/null +++ b/sources/agents/google_gemini_chat.toml @@ -0,0 +1,79 @@ +schema_version = 1 + +name = "Gemini Chat" +description = "Google Gemini (generateContent API) — coding chat assistant via the hosted Google AI provider." + +provider_instance = "Google AI" +endpoint = "/models/gemini-2.5-flash:streamGenerateContent?alt=sse" + +model = "gemini-2.5-flash" + +role = """ +You are a helpful coding assistant integrated into Qt Creator. +Answer concisely. When the user shares code, prefer concrete diffs or +minimal patches over rewriting whole files. Use markdown code blocks +with language tags so the IDE can render them. +""" + +enable_thinking = true +enable_tools = true + +tags = ["chat", "gemini", "google", "cloud"] + +context = """ +{%- set readme = read_file("${PROJECT_DIR}/README.md") -%} +{%- if length(readme) > 0 %} +## Project README.md +{{ readme }} +{%- endif %} +""" + +[template] +message_format = """ +{ + {%- if existsIn(ctx, "system_prompt") %} + "system_instruction": { "parts": [{ "text": {{ tojson(ctx.system_prompt) }} }] }, + {%- endif %} + "contents": [ + {%- for msg in ctx.history %} + { + "role": {% if msg.role == "assistant" %}"model"{% else %}"user"{% endif %}, + "parts": [ + {%- for b in msg.content_blocks %} + {%- if b.type == "text" %} + { "text": {{ tojson(b.text) }} } + {%- else if b.type == "thinking" %} + { "text": {{ tojson(b.thinking) }}, "thought": true, "thoughtSignature": {{ tojson(b.signature) }} } + {%- else if b.type == "tool_use" %} + { "functionCall": { "name": {{ tojson(b.name) }}, "args": {{ tojson(b.input) }} } } + {%- else if b.type == "tool_result" %} + { "functionResponse": { "name": {{ tojson(b.name) }}, "response": { "result": {{ tojson(b.content) }} } } } + {%- else if b.type == "image" %} + {%- if b.is_url %} + { "file_data": { "mime_type": {{ tojson(b.media_type) }}, "file_uri": {{ tojson(b.data) }} } } + {%- else %} + { "inline_data": { "mime_type": {{ tojson(b.media_type) }}, "data": {{ tojson(b.data) }} } } + {%- endif %} + {%- else %} + { "text": "" } + {%- endif %} + {% if not loop.is_last %},{% endif %} + {%- endfor %} + ] + }{% if not loop.is_last %},{% endif %} + {%- endfor %} + ] +} +""" + +[template.sampling.generationConfig] +maxOutputTokens = 8192 +temperature = 1 + +[template.thinking.request_block.generationConfig] +temperature = 1 +maxOutputTokens = 16000 + +[template.thinking.request_block.generationConfig.thinkingConfig] +includeThoughts = true +thinkingBudget = 8192 diff --git a/sources/providers/GenericProvider.cpp b/sources/providers/GenericProvider.cpp index e2bcc37..65c72d9 100644 --- a/sources/providers/GenericProvider.cpp +++ b/sources/providers/GenericProvider.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include #include @@ -58,6 +60,20 @@ QFuture> GenericProvider::getInstalledModels(const QString &url) return m_client->listModels(); } +RequestID GenericProvider::sendRequest( + const QUrl &url, const QJsonObject &payload, const QString &endpoint) +{ + // Gemini carries the model in the URL and rejects unknown body fields, so + // the model/stream keys injected by the generic pipeline must be dropped. + if (m_id == ProviderID::GoogleAI) { + QJsonObject cleaned = payload; + cleaned.remove("model"); + cleaned.remove("stream"); + return Provider::sendRequest(url, cleaned, endpoint); + } + return Provider::sendRequest(url, payload, endpoint); +} + namespace { using Cap = ProviderCapability; diff --git a/sources/providers/GenericProvider.hpp b/sources/providers/GenericProvider.hpp index e49c40d..d5ac608 100644 --- a/sources/providers/GenericProvider.hpp +++ b/sources/providers/GenericProvider.hpp @@ -36,6 +36,9 @@ public: ProviderCapabilities capabilities() const override; ::LLMQore::BaseClient *client() const override; + RequestID sendRequest( + const QUrl &url, const QJsonObject &payload, const QString &endpoint) override; + private: QString m_name; ProviderID m_id; diff --git a/sources/templates/JsonPromptTemplate.cpp b/sources/templates/JsonPromptTemplate.cpp index f45543c..6b7b0cb 100644 --- a/sources/templates/JsonPromptTemplate.cpp +++ b/sources/templates/JsonPromptTemplate.cpp @@ -5,6 +5,7 @@ #include "JsonPromptTemplate.hpp" #include +#include #include #include @@ -42,6 +43,16 @@ nlohmann::json buildContextJson(const ContextData &context) ctx["files_metadata"] = std::move(files); } + // tool_result blocks only carry the tool_use_id; resolve the originating + // tool name so templates (e.g. Google's functionResponse.name) can emit it. + QHash toolNameById; + if (context.history) { + for (const auto &msg : context.history.value()) + for (const auto &b : msg.blocks) + if (b.kind == ContentBlockEntry::Kind::ToolUse) + toolNameById.insert(b.toolUseId, b.toolName); + } + nlohmann::json history = nlohmann::json::array(); if (context.history) { for (const auto &msg : context.history.value()) { @@ -93,6 +104,7 @@ nlohmann::json buildContextJson(const ContextData &context) bj["type"] = "tool_result"; bj["tool_use_id"] = b.toolUseId.toStdString(); bj["content"] = b.result.toStdString(); + bj["name"] = toolNameById.value(b.toolUseId).toStdString(); break; case ContentBlockEntry::Kind::Image: bj["type"] = "image";