From ee37ed47bb26d9b98eef87839856b6ee433cf88d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=CE=A7=CE=B3=CF=86=CF=84=20Kompanion?=
 <chigammaphitau@chelydra.at>
Date: Sat, 18 Oct 2025 13:55:53 +0200
Subject: [PATCH] Finish the kllm integration better

Thanks to Gemini we now have a cleaner build interface
---
 CMakeLists.txt                     |  56 +--------
 src/CMakeLists.txt                 |   6 +
 src/KompanionAI/CMakeLists.txt     |   5 +-
 src/cli/CMakeLists.txt             |  12 ++
 src/gui/.CMakeLists.txt.swp        | Bin 0 -> 12288 bytes
 src/gui/CMakeLists.txt             |  17 +++
 src/gui/MainWindow.cpp             | 120 ++++++++++++++++++
 src/kllm/KIChatOptions.h           |  39 ------
 src/kllm/KIEmbedOptions.h          |  24 ----
 src/kllm/KIEmbeddingResult.h       |  23 ----
 src/kllm/KIError.h                 |  44 -------
 src/kllm/KIMessage.h               |  48 --------
 src/kllm/KIPolicy.h                |  28 -----
 src/kllm/KIProvider.h              |  43 -------
 src/kllm/KIReply.h                 |  53 --------
 src/kllm/KIToolSpec.h              |  65 ----------
 src/kllm/KLLMConstants.cpp         |  10 --
 src/kllm/KLLMConstants.h           |  16 ---
 src/kllm/KLLMContext.cpp           |  31 -----
 src/kllm/KLLMContext.h             |  48 --------
 src/kllm/KLLMInterface.cpp         | 126 -------------------
 src/kllm/KLLMInterface.h           |  43 -------
 src/kllm/KLLMOriginalInterface.cpp | 190 ----------------------------
 src/kllm/KLLMOriginalInterface.h   | 192 -----------------------------
 src/kllm/KLLMReply.cpp             | 106 ----------------
 src/kllm/KLLMReply.h               | 154 -----------------------
 src/kllm/KLLMRequest.cpp           |  49 --------
 src/kllm/KLLMRequest.h             |  80 ------------
 src/mcp/CMakeLists.txt             |  29 +++++
 src/mcp/HandlersIntrospection.hpp  |   5 +
 src/mcp/HandlersMemory.hpp         |   2 +-
 src/mcp/RegisterTools.hpp          |   1 +
 src/mcp/Tools.hpp                  |   5 +
 33 files changed, 205 insertions(+), 1465 deletions(-)
 create mode 100644 src/CMakeLists.txt
 create mode 100644 src/cli/CMakeLists.txt
 create mode 100644 src/gui/.CMakeLists.txt.swp
 create mode 100644 src/gui/CMakeLists.txt
 create mode 100644 src/gui/MainWindow.cpp
 delete mode 100644 src/kllm/KIChatOptions.h
 delete mode 100644 src/kllm/KIEmbedOptions.h
 delete mode 100644 src/kllm/KIEmbeddingResult.h
 delete mode 100644 src/kllm/KIError.h
 delete mode 100644 src/kllm/KIMessage.h
 delete mode 100644 src/kllm/KIPolicy.h
 delete mode 100644 src/kllm/KIProvider.h
 delete mode 100644 src/kllm/KIReply.h
 delete mode 100644 src/kllm/KIToolSpec.h
 delete mode 100644 src/kllm/KLLMConstants.cpp
 delete mode 100644 src/kllm/KLLMConstants.h
 delete mode 100644 src/kllm/KLLMContext.cpp
 delete mode 100644 src/kllm/KLLMContext.h
 delete mode 100644 src/kllm/KLLMInterface.cpp
 delete mode 100644 src/kllm/KLLMInterface.h
 delete mode 100644 src/kllm/KLLMOriginalInterface.cpp
 delete mode 100644 src/kllm/KLLMOriginalInterface.h
 delete mode 100644 src/kllm/KLLMReply.cpp
 delete mode 100644 src/kllm/KLLMReply.h
 delete mode 100644 src/kllm/KLLMRequest.cpp
 delete mode 100644 src/kllm/KLLMRequest.h
 create mode 100644 src/mcp/CMakeLists.txt

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7bc5c60..d0ae5f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,7 +26,6 @@ include(KDEClangFormat)
 include(ECMDeprecationSettings)
 
 include(KDEGitCommitHooks)
-
 find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
     Core
     Network
@@ -36,14 +35,11 @@ find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
 find_package(Qt6McpServer CONFIG REQUIRED)
 find_package(Qt6McpCommon CONFIG REQUIRED)
 
-option(KOMPANION_USE_GUI "Build optional GUI components using Qt6Gui" ON)
-if (KOMPANION_USE_GUI)
-    find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Gui)
-endif()
+find_package(Qt6 ${KF6_MIN_VERSON} CONFIG REQUIRED COMPONENTS Gui)
+find_package(KF6Config ${KF6_MIN_VERSION} CONFIG REQUIRED)
+find_package(KF6Parts ${KF6_MIN_VERSION} CONFIG REQUIRED)
+find_package(KF6TextEditor ${KF6_MIN_VERSION} CONFIG REQUIRED)
 
-find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
-    Config
-)
 find_package(Qt6Test ${QT_MIN_VERSION} CONFIG QUIET)
 set_package_properties(Qt6Test PROPERTIES
          PURPOSE "Required for tests"
@@ -55,52 +51,10 @@ add_feature_info("Qt6Test" Qt6Test_FOUND "Required for building tests")
 set(KOMPANION_DB_INIT_INSTALL_DIR "${KDE_INSTALL_FULL_DATADIR}/kompanion/db/init")
 install(DIRECTORY db/init/ DESTINATION ${KDE_INSTALL_DATADIR}/kompanion/db/init FILES_MATCHING PATTERN "*.sql")
 
-add_subdirectory(src/dal)
-add_subdirectory(src/KompanionAI)
-
-add_executable(kom_mcp
-    src/main.cpp
-    src/mcp/KompanionQtServer.cpp
-)
-target_include_directories(kom_mcp PRIVATE src)
-qt_add_resources(kom_mcp kompanion_mcp_resources
-    PREFIX "/kompanion"
-    BASE "src/mcp"
-    FILES src/mcp/ToolSchemas.json
-)
-target_link_libraries(kom_mcp PRIVATE
-    kom_dal
-    kom_ai
-    KF6::ConfigCore
-    Qt6::Core
-    Qt6::Network
-    Qt6::McpServer
-    Qt6::McpCommon
-)
-target_compile_options(kom_mcp PRIVATE -fexceptions)
-target_compile_definitions(kom_mcp PRIVATE
-  PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
-  KOMPANION_DB_INIT_INSTALL_DIR="${KOMPANION_DB_INIT_INSTALL_DIR}"
-)
-
-install(TARGETS kom_mcp RUNTIME DESTINATION bin)
-install(FILES src/mcp/ToolSchemas.json DESTINATION ${KDE_INSTALL_DATADIR}/kompanion/mcp)
+add_subdirectory(src)
 
 option(BUILD_TESTS "Build tests" ON)
 
-add_executable(kompanion
-src/cli/KompanionApp.cpp
-)
-target_include_directories(kompanion PRIVATE src)
-target_link_libraries(kompanion PRIVATE
-Qt6::Core
-Qt6::Sql
-KF6::ConfigCore
-kom_dal
-kom_ai
-)
-install(TARGETS kompanion RUNTIME ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
-
 if (BUILD_TESTS)
   enable_testing()
   add_subdirectory(tests)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..b2fba5c
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_subdirectory(cli)
+add_subdirectory(dal)
+add_subdirectory(gui)
+
+add_subdirectory(KompanionAI)
+add_subdirectory(mcp)
diff --git a/src/KompanionAI/CMakeLists.txt b/src/KompanionAI/CMakeLists.txt
index 794da25..99a5557 100644
--- a/src/KompanionAI/CMakeLists.txt
+++ b/src/KompanionAI/CMakeLists.txt
@@ -22,7 +22,10 @@ set(KOMPANION_AI_HDRS
 
 add_library(kom_ai STATIC ${KOMPANION_AI_SRCS} ${KOMPANION_AI_HDRS})
 
-target_include_directories(kom_ai PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+target_include_directories(kom_ai PUBLIC
+    ${CMAKE_CURRENT_SOURCE_DIR}
+    ${Qt6Core_INCLUDE_DIRS}
+)
 
 target_link_libraries(kom_ai PUBLIC
     Qt6::Core
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
new file mode 100644
index 0000000..5efd624
--- /dev/null
+++ b/src/cli/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(kompanion
+KompanionApp.cpp
+)
+target_include_directories(kompanion PRIVATE ../)
+target_link_libraries(kompanion PRIVATE
+Qt6::Core
+Qt6::Sql
+KF6::ConfigCore
+kom_dal
+kom_ai
+)
+install(TARGETS kompanion RUNTIME ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/src/gui/.CMakeLists.txt.swp b/src/gui/.CMakeLists.txt.swp
new file mode 100644
index 0000000000000000000000000000000000000000..675b6e387d05b4d1e7f02b4da3445f48184915ef
GIT binary patch
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
new file mode 100644
index 0000000..a6db934
--- /dev/null
+++ b/src/gui/CMakeLists.txt
@@ -0,0 +1,17 @@
+include_directories($CMAKE_SRC_DIR/src)
+add_executable(kompanion_gui
+    MainWindow.cpp
+)
+target_include_directories(kompanion_gui PRIVATE ${PROJECT_SOURCE_DIR}/src)
+
+target_link_libraries(kompanion_gui PRIVATE
+    KF6::Parts
+    KF6::TextEditor
+    KF6::ConfigCore
+    Qt6::McpServer
+    Qt6::McpCommon
+    kom_dal
+    kom_ai
+)
+
+install(TARGETS kompanion_gui RUNTIME DESTINATION bin)
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
new file mode 100644
index 0000000..713eee9
--- /dev/null
+++ b/src/gui/MainWindow.cpp
@@ -0,0 +1,120 @@
+#include <KParts/MainWindow>
+#include <KTextEditor/Editor>
+#include <KTextEditor/View>
+#include <KTextEditor/Document>
+#include <KTextEditor/Cursor>
+
+#include <QVBoxLayout>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QApplication>
+#include <QFutureWatcher>
+
+#include <KompanionAI/Client/KIClient.h>
+#include <KompanionAI/Provider/OllamaProvider.h>
+#include <KompanionAI/Message/KIMessage.h>
+#include <KompanionAI/Message/KIThread.h>
+#include <KompanionAI/Completion/KIReply.h>
+#include <KompanionAI/Completion/KIChatOptions.h>
+
+
+class MainWindow : public KParts::MainWindow
+{
+public:
+    MainWindow(QWidget *parent = nullptr) : KParts::MainWindow(parent)
+    {
+        QWidget *mainWidget = new QWidget(this);
+        setCentralWidget(mainWidget);
+
+        QVBoxLayout *layout = new QVBoxLayout(mainWidget);
+
+        auto editor = KTextEditor::Editor::instance();
+        m_document = editor->createDocument(this);
+        m_chatView = m_document->createView(mainWidget);
+        m_document->setReadWrite(false);
+        layout->addWidget(m_chatView);
+
+        m_chatInput = new QLineEdit(mainWidget);
+        layout->addWidget(m_chatInput);
+
+        QPushButton *sendButton = new QPushButton("Send", mainWidget);
+        layout->addWidget(sendButton);
+
+        connect(sendButton, &QPushButton::clicked, this, &MainWindow::sendMessage);
+
+        // Setup KompanionAI
+        m_ollamaProvider = new KompanionAI::OllamaProvider(this);
+        m_kompanionClient = new KompanionAI::KIClient(this);
+        m_kompanionClient->setProvider(m_ollamaProvider);
+        m_kompanionClient->setDefaultModel("llama2"); // Or some other default
+    }
+
+private slots:
+    void sendMessage()
+    {
+        const QString message = m_chatInput->text();
+        if (message.isEmpty()) {
+            return;
+        }
+
+        m_chatInput->clear();
+
+        // Append user message to chat view
+        insertText(QString("User: %1\n").arg(message));
+
+        // Send message to KompanionAI
+        KompanionAI::KIThread thread;
+        KompanionAI::KIMessage kimessage;
+        kimessage.role = "user";
+        KompanionAI::KIMessagePart part;
+        part.mime = "text/plain";
+        part.text = message;
+        kimessage.parts.append(part);
+        thread.messages.append(kimessage);
+
+        KompanionAI::KIChatOptions opts;
+        opts.model = m_kompanionClient->defaultModel();
+
+        QFuture<KompanionAI::KIReply*> future = m_kompanionClient->chat(thread, opts);
+        
+        QFutureWatcher<KompanionAI::KIReply*> *watcher = new QFutureWatcher<KompanionAI::KIReply*>(this);
+        connect(watcher, &QFutureWatcher<KompanionAI::KIReply*>::finished, this, [this, watcher]() {
+            KompanionAI::KIReply* reply = watcher->result();
+            connect(reply, &KompanionAI::KIReply::tokensAdded, this, [this](const QString& delta) {
+                insertText(delta);
+            });
+            
+            connect(reply, &KompanionAI::KIReply::finished, this, [this, reply]() {
+                insertText("\n");
+                reply->deleteLater();
+            });
+            watcher->deleteLater();
+        });
+        watcher->setFuture(future);
+    }
+
+private:
+    void insertText(const QString &text)
+    {
+        KTextEditor::Cursor endCursor(m_document->lines() - 1, m_document->lineLength(m_document->lines() - 1));
+        m_document->insertText(endCursor, text);
+    }
+
+private:
+    KTextEditor::View *m_chatView;
+    KTextEditor::Document *m_document;
+    QLineEdit *m_chatInput;
+
+    KompanionAI::KIClient* m_kompanionClient;
+    KompanionAI::OllamaProvider* m_ollamaProvider;
+};
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+
+    MainWindow window;
+    window.show();
+
+    return app.exec();
+}
\ No newline at end of file
diff --git a/src/kllm/KIChatOptions.h b/src/kllm/KIChatOptions.h
deleted file mode 100644
index 66e27d9..0000000
--- a/src/kllm/KIChatOptions.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-#include <QString>
-#include <QList>
-
-#include "KIToolSpec.h"
-#include "KIPolicy.h"
-
-namespace KompanionAI
-{
-
-class KIChatOptions
-{
-    Q_GADGET
-    Q_PROPERTY(QString model MEMBER model)
-    Q_PROPERTY(bool stream MEMBER stream)
-    Q_PROPERTY(bool jsonMode MEMBER jsonMode)
-    Q_PROPERTY(int maxTokens MEMBER maxTokens)
-    Q_PROPERTY(double temperature MEMBER temperature)
-    Q_PROPERTY(QList<KIToolSpec> tools MEMBER tools)
-    Q_PROPERTY(KIPolicy policy MEMBER policy)
-
-public:
-    QString model;
-    bool stream = true;
-    bool jsonMode = false;
-    int maxTokens = 512;
-    double temperature = 0.2;
-    QList<KIToolSpec> tools;
-    KIPolicy policy;
-};
-
-}
diff --git a/src/kllm/KIEmbedOptions.h b/src/kllm/KIEmbedOptions.h
deleted file mode 100644
index 44c4640..0000000
--- a/src/kllm/KIEmbedOptions.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QString>
-
-namespace KompanionAI
-{
-
-class KIEmbedOptions
-{
-    Q_GADGET
-    Q_PROPERTY(QString model MEMBER model)
-    Q_PROPERTY(QString normalize MEMBER normalize)
-
-public:
-    QString model = "text-embed-local";
-    QString normalize = "l2";
-};
-
-}
diff --git a/src/kllm/KIEmbeddingResult.h b/src/kllm/KIEmbeddingResult.h
deleted file mode 100644
index e649e3d..0000000
--- a/src/kllm/KIEmbeddingResult.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QVector>
-#include <QString>
-
-namespace KompanionAI
-{
-
-class KIEmbeddingResult
-{
-    Q_GADGET
-
-public:
-    QVector<QVector<float>> vectors;
-    QString model;
-};
-
-}
diff --git a/src/kllm/KIError.h b/src/kllm/KIError.h
deleted file mode 100644
index 8c89259..0000000
--- a/src/kllm/KIError.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QString>
-
-namespace KompanionAI
-{
-
-class KIError
-{
-public:
-    enum ErrorCode {
-        NoError,
-        NetworkError,
-        ApiError,
-        InvalidJson,
-        Cancelled
-    };
-
-    KIError(ErrorCode code = NoError, const QString &message = QString(), int httpStatus = 0, int retryAfter = 0)
-        : m_code(code)
-        , m_message(message)
-        , m_httpStatus(httpStatus)
-        , m_retryAfter(retryAfter)
-    {
-    }
-
-    ErrorCode code() const { return m_code; }
-    QString message() const { return m_message; }
-    int httpStatus() const { return m_httpStatus; }
-    int retryAfter() const { return m_retryAfter; }
-
-private:
-    ErrorCode m_code;
-    QString m_message;
-    int m_httpStatus;
-    int m_retryAfter;
-};
-
-}
diff --git a/src/kllm/KIMessage.h b/src/kllm/KIMessage.h
deleted file mode 100644
index 670cde2..0000000
--- a/src/kllm/KIMessage.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-#include <QString>
-#include <QList>
-#include <QVariantMap>
-
-namespace KompanionAI
-{
-
-class KIMessagePart
-{
-    Q_GADGET
-    Q_PROPERTY(QString mime READ mime)
-    Q_PROPERTY(QString text READ text)
-
-public:
-    QString mime;
-    QString text;
-};
-
-class KIMessage
-{
-    Q_GADGET
-    Q_PROPERTY(QString role READ role)
-    Q_PROPERTY(QList<KIMessagePart> parts READ parts)
-
-public:
-    QString role;
-    QList<KIMessagePart> parts;
-    QVariantMap metadata;
-};
-
-class KIThread
-{
-    Q_GADGET
-    Q_PROPERTY(QList<KIMessage> messages READ messages)
-
-public:
-    QList<KIMessage> messages;
-};
-
-}
\ No newline at end of file
diff --git a/src/kllm/KIPolicy.h b/src/kllm/KIPolicy.h
deleted file mode 100644
index 1c89991..0000000
--- a/src/kllm/KIPolicy.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-#include <QString>
-#include <QStringList>
-
-namespace KompanionAI
-{
-
-class KIPolicy
-{
-    Q_GADGET
-    Q_PROPERTY(QString visibility MEMBER visibility)
-    Q_PROPERTY(bool allowNetwork MEMBER allowNetwork)
-    Q_PROPERTY(QStringList redactions MEMBER redactions)
-
-public:
-    QString visibility = "private";
-    bool allowNetwork = false;
-    QStringList redactions;
-};
-
-}
diff --git a/src/kllm/KIProvider.h b/src/kllm/KIProvider.h
deleted file mode 100644
index b4247d4..0000000
--- a/src/kllm/KIProvider.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-#include <QStringList>
-#include <QFuture>
-
-#include "KIReply.h"
-#include "KIThread.h"
-#include "KIChatOptions.h"
-#include "KIEmbeddingResult.h"
-#include "KIEmbedOptions.h"
-#include "KICapabilities.h"
-
-namespace KompanionAI
-{
-
-class KIProvider : public QObject
-{
-    Q_OBJECT
-    Q_PROPERTY(QString name READ name CONSTANT)
-    Q_PROPERTY(QStringList models READ models NOTIFY modelsChanged)
-    Q_PROPERTY(KICapabilities caps READ caps CONSTANT)
-
-public:
-    explicit KIProvider(QObject *parent = nullptr);
-
-    virtual QString name() const = 0;
-    virtual QStringList models() const = 0;
-    virtual KICapabilities caps() const = 0;
-
-    virtual QFuture<KIReply*> chat(const KIThread &thread, const KIChatOptions &opts) = 0;
-    virtual QFuture<KIEmbeddingResult> embed(const QStringList &texts, const KIEmbedOptions &opts) = 0;
-
-Q_SIGNALS:
-    void modelsChanged();
-};
-
-}
diff --git a/src/kllm/KIReply.h b/src/kllm/KIReply.h
deleted file mode 100644
index 127960d..0000000
--- a/src/kllm/KIReply.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-#include <QString>
-#include <QVariantMap>
-
-#include "KIToolSpec.h"
-
-namespace KompanionAI
-{
-
-class KIError;
-
-class KIReply : public QObject
-{
-    Q_OBJECT
-    Q_PROPERTY(bool finished READ isFinished NOTIFY finishedChanged)
-    Q_PROPERTY(int promptTokens READ promptTokens CONSTANT)
-    Q_PROPERTY(int completionTokens READ completionTokens CONSTANT)
-    Q_PROPERTY(QString model READ model CONSTANT)
-
-public:
-    explicit KIReply(QObject *parent = nullptr);
-
-    Q_INVOKABLE QString text() const;
-    bool isFinished() const;
-    int promptTokens() const;
-    int completionTokens() const;
-    QString model() const;
-
-Q_SIGNALS:
-    void tokensAdded(const QString &delta);
-    void toolCallProposed(const KIToolCall &call);
-    void toolResultRequested(const KIToolCall &call);
-    void traceEvent(const QVariantMap &span);
-    void finished();
-    void errorOccurred(const KIError &error);
-    void finishedChanged();
-
-private:
-    bool m_finished = false;
-    int m_promptTokens = 0;
-    int m_completionTokens = 0;
-    QString m_model;
-    QString m_text;
-};
-
-}
\ No newline at end of file
diff --git a/src/kllm/KIToolSpec.h b/src/kllm/KIToolSpec.h
deleted file mode 100644
index ba9fa08..0000000
--- a/src/kllm/KIToolSpec.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include <QObject>
-#include <QString>
-#include <QList>
-#include <QVariant>
-#include <QVariantMap>
-
-namespace KompanionAI
-{
-
-class KIToolParam
-{
-    Q_GADGET
-    Q_PROPERTY(QString name READ name)
-    Q_PROPERTY(QString type READ type)
-    Q_PROPERTY(bool required READ required)
-    Q_PROPERTY(QVariant defaultValue READ defaultValue)
-
-public:
-    QString name, type;
-    bool required = false;
-    QVariant defaultValue;
-};
-
-class KIToolSpec
-{
-    Q_GADGET
-    Q_PROPERTY(QString name READ name)
-    Q_PROPERTY(QString description READ description)
-    Q_PROPERTY(QList<KIToolParam> params READ params)
-
-public:
-    QString name, description;
-    QList<KIToolParam> params;
-};
-
-class KIToolCall
-{
-    Q_GADGET
-    Q_PROPERTY(QString name READ name)
-    Q_PROPERTY(QVariantMap arguments READ arguments)
-
-public:
-    QString name;
-    QVariantMap arguments;
-};
-
-class KIToolResult
-{
-    Q_GADGET
-    Q_PROPERTY(QString name READ name)
-    Q_PROPERTY(QVariant result READ result)
-
-public:
-    QString name;
-    QVariant result;
-};
-
-}
\ No newline at end of file
diff --git a/src/kllm/KLLMConstants.cpp b/src/kllm/KLLMConstants.cpp
deleted file mode 100644
index 19493c8..0000000
--- a/src/kllm/KLLMConstants.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-// SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#include "KLLMConstants.h"
-
-QUrl KLLMCore::ollamaUrl()
-{
-    return QUrl(QStringLiteral("http://127.0.0.1:11434"));
-}
diff --git a/src/kllm/KLLMConstants.h b/src/kllm/KLLMConstants.h
deleted file mode 100644
index 7a306b9..0000000
--- a/src/kllm/KLLMConstants.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-#include "kllmcore_export.h"
-#include <QUrl>
-namespace KLLMCore
-{
-/**
- * @brief Return default Ollama Url
- * @return default Ollama Url.
- */
-[[nodiscard]] KLLMCORE_EXPORT QUrl ollamaUrl();
-
-}
diff --git a/src/kllm/KLLMContext.cpp b/src/kllm/KLLMContext.cpp
deleted file mode 100644
index 99481ee..0000000
--- a/src/kllm/KLLMContext.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#include "KLLMContext.h"
-
-#include <QJsonValue>
-
-using namespace KLLMCore;
-
-QJsonValue KLLMContext::toJson() const
-{
-    switch (m_backend) {
-    case Backend::Ollama:
-        return m_data.value<QJsonArray>();
-    default:
-        return {};
-    }
-}
-
-void KLLMContext::setOllamaContext(const QJsonArray &context)
-{
-    m_data = context;
-    m_backend = Backend::Ollama;
-}
-
-QDebug operator<<(QDebug d, const KLLMContext &t)
-{
-    d << "m_data " << t.toJson();
-    return d;
-}
diff --git a/src/kllm/KLLMContext.h b/src/kllm/KLLMContext.h
deleted file mode 100644
index 1129268..0000000
--- a/src/kllm/KLLMContext.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-#include "kllmcore_export.h"
-#include <QDebug>
-#include <QJsonArray>
-
-namespace KLLMCore
-{
-/**
- * @brief KLLMContext provides a representation of a conversation context.
- *
- * Most, if not all, LLMs have the concept of "context". This allows them to refer to previous messages in a conversation to
- * enhance their replies. In most scenarios, this is the preferred behavior.
- *
- * To use KLLMContext, you simply need to get the context from each KLLMReply and set it on the next KLLMReqeust that you
- * send. KLLMInterface will use this in KLLMInterface::getCompletion().
- */
-struct KLLMCORE_EXPORT KLLMContext {
-    /**
-     * @brief Converts the context to a JSON representation.
-     *
-     * Different LLM backends represent context in different ways; for example, while Ollama represents context as an array
-     * of integer identifiers, OpenAI relies on a JSON array of all the messages in the conversation so far. Therefore, this
-     * function exists to take any representation set on it for any backend and convert it to a JSON value suitable for
-     * sending in a request.
-     *
-     * @return A JSON representation of the context.
-     */
-    [[nodiscard]] QJsonValue toJson() const;
-
-    /**
-     * @brief Sets an Ollama context as the current context.
-     * @param context The context from Ollama.
-     */
-    void setOllamaContext(const QJsonArray &context);
-
-private:
-    enum class Backend {
-        Ollama,
-    } m_backend;
-    QVariant m_data;
-};
-}
-KLLMCORE_EXPORT QDebug operator<<(QDebug d, const KLLMCore::KLLMContext &t);
diff --git a/src/kllm/KLLMInterface.cpp b/src/kllm/KLLMInterface.cpp
deleted file mode 100644
index 9f36725..0000000
--- a/src/kllm/KLLMInterface.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-
-#include "KLLMInterface.h"
-#include <KLocalizedString>
-
-using namespace Qt::StringLiterals;
-using namespace KLLMCore;
-
-KLLMInterface::KLLMInterface(QObject *parent)
-    : KLLMInterface{QString{}, parent}
-{
-}
-
-KLLMInterface::KLLMInterface(const QString &ollamaUrl, QObject *parent)
-    : KLLMOriginalInterface{ollamaUrl, parent}
-    , m_kompanionClient{new KompanionAI::KIClient(this)}
-    , m_ollamaProvider{new KompanionAI::OllamaProvider(this)}
-{
-    m_kompanionClient->setProvider(m_ollamaProvider);
-    setOllamaUrl(ollamaUrl);
-}
-
-KLLMInterface::KLLMInterface(const QUrl &ollamaUrl, QObject *parent)
-    : KLLMInterface{ollamaUrl.toString(), parent}
-{
-}
-
-bool KLLMInterface::ready() const
-{
-    // For now, assume ready if provider is set and has models
-    return m_kompanionClient->provider() != nullptr && !m_ollamaProvider->models().isEmpty();
-}
-
-bool KLLMInterface::hasError() const
-{
-    // TODO: Implement proper error checking from KompanionAI client
-    return false;
-}
-
-QStringList KLLMInterface::models() const
-{
-    return m_ollamaProvider->models();
-}
-
-KLLMReply *KLLMInterface::getCompletion(const KLLMRequest &request)
-{
-    KompanionAI::KIThread thread;
-    KompanionAI::KIMessage message;
-    message.role = "user";
-    KompanionAI::KIMessagePart part;
-    part.mime = "text/plain";
-    part.text = request.message();
-    message.parts.append(part);
-    thread.messages.append(message);
-
-    KompanionAI::KIChatOptions opts;
-    opts.model = request.model().isEmpty() ? m_kompanionClient->defaultModel() : request.model();
-    // TODO: Map KLLMContext to KompanionAI thread/options
-
-    auto kiReply = m_kompanionClient->chat(thread, opts).result();
-    auto kllmReply = new KLLMReply(this);
-
-    connect(kiReply, &KompanionAI::KIReply::tokensAdded, kllmReply, [kllmReply](const QString& delta) {
-        kllmReply->addContent(delta);
-    });
-
-    connect(kiReply, &KompanionAI::KIReply::finished, kllmReply, [kllmReply]() {
-        kllmReply->setFinished(true);
-    });
-
-    connect(kiReply, &KompanionAI::KIReply::errorOccurred, kllmReply, [kllmReply](const KompanionAI::KIError& error) {
-        kllmReply->setError(error.message);
-    });
-
-    return kllmReply;
-}
-
-KLLMReply *KLLMInterface::getModelInfo(const KLLMRequest &request)
-{
-    // This is a simplified implementation as KompanionAI does not have a direct getModelInfo.
-    // We will return a KLLMReply with the model name if it exists in the provider's models.
-    auto kllmReply = new KLLMReply(this);
-    if (m_ollamaProvider->models().contains(request.model())) {
-        kllmReply->addContent(QString("{ \"model\": \"%1\" }").arg(request.model()));
-    } else {
-        kllmReply->setError(i18n("Model %1 not found.", request.model()));
-    }
-    kllmReply->setFinished(true);
-    return kllmReply;
-}
-
-void KLLMInterface::reload()
-{
-    m_ollamaProvider->reload();
-}
-
-QString KLLMInterface::ollamaUrl() const
-{
-    // Ollama URL is managed by the OllamaProvider internally in KompanionAI
-    // For compatibility, we return an empty string or a placeholder.
-    return QString();
-}
-
-void KLLMInterface::setOllamaUrl(const QString &ollamaUrl)
-{
-    // In KompanionAI, the Ollama URL is configured directly on the OllamaProvider.
-    // For compatibility, we can re-instantiate the provider or update its internal URL.
-    // For now, we'll just set the default model if the URL is treated as a model name.
-    // This needs proper handling if the URL is truly meant for provider configuration.
-    m_kompanionClient->setDefaultModel(ollamaUrl);
-}
-
-void KLLMInterface::setOllamaUrl(const QUrl &ollamaUrl)
-{
-    setOllamaUrl(ollamaUrl.toString());
-}
-
-QString KLLMInterface::systemPrompt() const
-{
-    // TODO: Extract system prompt from KompanionAI thread if available
-    return QString();
-}
-
-void KLLMInterface::setSystemPrompt(const QString &systemPrompt)
-{
-    // TODO: Set system prompt in KompanionAI thread
-}
diff --git a/src/kllm/KLLMInterface.h b/src/kllm/KLLMInterface.h
deleted file mode 100644
index 762738a..0000000
--- a/src/kllm/KLLMInterface.h
+++ /dev/null
@@ -1,43 +0,0 @@
-
-#ifndef KLLMINTERFACE_H
-#define KLLMINTERFACE_H
-
-#include "KLLMOriginalInterface.h"
-#include "KompanionAI/Client/KIClient.h"
-#include "KompanionAI/Provider/OllamaProvider.h"
-
-namespace KLLMCore {
-
-class KLLMInterface : public KLLMOriginalInterface
-{
-    Q_OBJECT
-
-public:
-    explicit KLLMCompatInterface(QObject *parent = nullptr);
-    explicit KLLMCompatInterface(const QString &ollamaUrl, QObject *parent = nullptr);
-    explicit KLLMCompatInterface(const QUrl &ollamaUrl, QObject *parent = nullptr);
-
-    bool ready() const override;
-    bool hasError() const override;
-    QStringList models() const override;
-
-    KLLMReply *getCompletion(const KLLMRequest &request) override;
-    KLLMReply *getModelInfo(const KLLMRequest &request) override;
-
-    void reload() override;
-
-    QString ollamaUrl() const override;
-    void setOllamaUrl(const QString &ollamaUrl) override;
-    void setOllamaUrl(const QUrl &ollamaUrl) override;
-
-    QString systemPrompt() const override;
-    void setSystemPrompt(const QString &systemPrompt) override;
-
-private:
-    KompanionAI::KIClient* m_kompanionClient;
-    KompanionAI::OllamaProvider* m_ollamaProvider;
-};
-
-} // namespace KLLMCore
-
-#endif // KLLMCOMPAT_H
diff --git a/src/kllm/KLLMOriginalInterface.cpp b/src/kllm/KLLMOriginalInterface.cpp
deleted file mode 100644
index 13c26a2..0000000
--- a/src/kllm/KLLMOriginalInterface.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#include "KLLMInterface.h"
-
-#include <KLocalizedString>
-
-#include <QBuffer>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QNetworkAccessManager>
-#include <QNetworkReply>
-
-using namespace Qt::StringLiterals;
-using namespace KLLMCore;
-
-KLLMInterface::KLLMInterface(QObject *parent)
-    : KLLMInterface{QString{}, parent}
-{
-}
-
-KLLMInterface::KLLMInterface(const QString &ollamaUrl, QObject *parent)
-    : QObject{parent}
-    , m_manager{new QNetworkAccessManager{this}}
-    , m_ollamaUrl{ollamaUrl}
-{
-    if (!m_ollamaUrl.isEmpty())
-        reload();
-}
-
-KLLMInterface::KLLMInterface(const QUrl &ollamaUrl, QObject *parent)
-    : KLLMInterface{ollamaUrl.toString(), parent}
-{
-}
-
-bool KLLMInterface::ready() const
-{
-    return m_ready && !m_hasError;
-}
-
-bool KLLMInterface::hasError() const
-{
-    return m_hasError;
-}
-
-QStringList KLLMInterface::models() const
-{
-    return m_models;
-}
-
-#if 0
-void KLLMInterface::deleteModel(const QString &modelName)
-{
-    Q_ASSERT(ready());
-
-    QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/delete"))};
-    req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
-    QJsonObject data;
-    data["name"_L1] = modelName;
-
-    // Delete resource doesn't take argument. Need to look at how to do it.
-    auto buf = new QBuffer{this};
-    buf->setData(QJsonDocument(data).toJson(QJsonDocument::Compact));
-
-    auto reply = new KLLMReply{m_manager->deleteResource(req, buf), this};
-    connect(reply, &KLLMReply::finished, this, [this, reply, buf] {
-        Q_EMIT finished(reply->readResponse());
-        buf->deleteLater();
-    });
-}
-#endif
-
-KLLMReply *KLLMInterface::getCompletion(const KLLMRequest &request)
-{
-    Q_ASSERT(ready());
-
-    QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/generate"))};
-    req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
-
-    QJsonObject data;
-    data["model"_L1] = request.model().isEmpty() ? m_models.constFirst() : request.model();
-    data["prompt"_L1] = request.message();
-
-    const auto context = request.context().toJson();
-    if (!context.isNull()) {
-        data["context"_L1] = context;
-    }
-
-    if (!m_systemPrompt.isEmpty()) {
-        data["system"_L1] = m_systemPrompt;
-    }
-
-    auto buf = new QBuffer{this};
-    buf->setData(QJsonDocument(data).toJson(QJsonDocument::Compact));
-
-    auto reply = new KLLMReply{m_manager->post(req, buf), this};
-    connect(reply, &KLLMReply::finished, this, [this, reply, buf] {
-        Q_EMIT finished(reply->readResponse());
-        buf->deleteLater();
-    });
-    return reply;
-}
-
-KLLMReply *KLLMInterface::getModelInfo(const KLLMRequest &request)
-{
-    Q_ASSERT(ready());
-
-    QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/show"))};
-    req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
-
-    QJsonObject data;
-    data["model"_L1] = request.model().isEmpty() ? m_models.constFirst() : request.model();
-
-    auto buf = new QBuffer{this};
-    buf->setData(QJsonDocument(data).toJson(QJsonDocument::Compact));
-
-    auto reply = new KLLMReply{m_manager->post(req, buf), this, KLLMReply::RequestTypes::Show};
-    connect(reply, &KLLMReply::finished, this, [this, reply, buf] {
-        Q_EMIT finished(reply->readResponse());
-        buf->deleteLater();
-    });
-    return reply;
-}
-
-void KLLMInterface::reload()
-{
-    if (m_ollamaCheck)
-        disconnect(m_ollamaCheck);
-
-    QNetworkRequest req{QUrl::fromUserInput(m_ollamaUrl + QStringLiteral("/api/tags"))};
-    req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
-    auto rep = m_manager->get(req);
-    m_ollamaCheck = connect(rep, &QNetworkReply::finished, this, [this, rep] {
-        if (rep->error() != QNetworkReply::NoError) {
-            Q_EMIT errorOccurred(i18n("Failed to connect to interface at %1: %2", m_ollamaUrl, rep->errorString()));
-            m_hasError = true;
-            Q_EMIT readyChanged();
-            Q_EMIT hasErrorChanged();
-            return;
-        }
-
-        const auto json = QJsonDocument::fromJson(rep->readAll());
-        const auto models = json["models"_L1].toArray();
-        for (const QJsonValue &model : models) {
-            m_models.push_back(model["name"_L1].toString());
-        }
-        Q_EMIT modelsChanged();
-
-        m_ready = !m_models.isEmpty();
-        m_hasError = false;
-        Q_EMIT readyChanged();
-        Q_EMIT hasErrorChanged();
-    });
-}
-
-QString KLLMInterface::ollamaUrl() const
-{
-    return m_ollamaUrl;
-}
-
-void KLLMInterface::setOllamaUrl(const QString &ollamaUrl)
-{
-    if (m_ollamaUrl == ollamaUrl)
-        return;
-    m_ollamaUrl = ollamaUrl;
-    Q_EMIT ollamaUrlChanged();
-    reload();
-}
-
-void KLLMInterface::setOllamaUrl(const QUrl &ollamaUrl)
-{
-    setOllamaUrl(ollamaUrl.toString());
-}
-
-QString KLLMInterface::systemPrompt() const
-{
-    return m_systemPrompt;
-}
-
-void KLLMInterface::setSystemPrompt(const QString &systemPrompt)
-{
-    if (m_systemPrompt == systemPrompt)
-        return;
-    m_systemPrompt = systemPrompt;
-    Q_EMIT systemPromptChanged();
-}
-
-#include "moc_KLLMInterface.cpp"
diff --git a/src/kllm/KLLMOriginalInterface.h b/src/kllm/KLLMOriginalInterface.h
deleted file mode 100644
index 5240455..0000000
--- a/src/kllm/KLLMOriginalInterface.h
+++ /dev/null
@@ -1,192 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include "kllmcore_export.h"
-
-#include "KLLMReply.h"
-#include "KLLMRequest.h"
-class QNetworkAccessManager;
-namespace KLLMCore
-{
-/**
- * @brief The KLLMInterface class provides an interface around the LLM API.
- *
- * KLLM is designed to feel similar to Qt Network. This means that all LLM requests must be routed through a central
- * KLLMInterface object.
- *
- * To request a message or completion from the LLM, first create a KLLMRequest object with the desired message. Choose the
- * model from models() that you wish to use for this request and set it on the KLLMRequest. Then call getCompletion(),
- * passing your KLLMRequest object. You will recieve a KLLMReply object; connect to KLLMReply::contentAdded() if you wish to
- * receive content updates as they arrive from the server or connect to KLLMReply::finished() if you prefer to have the whole
- * message delivered at once.
- *
- * You should not request any completions (or otherwise use this class) until ready() returns true. Using the interface
- * before it is ready can cause problems; for example, the interface may not have loaded the available models yet or the
- * backend could be unreachable.
- */
-class KLLMCORE_EXPORT KLLMInterface : public QObject
-{
-    Q_OBJECT
-
-    Q_PROPERTY(bool ready READ ready NOTIFY readyChanged FINAL)
-    Q_PROPERTY(bool hasError READ hasError NOTIFY hasErrorChanged FINAL)
-    Q_PROPERTY(QStringList models READ models NOTIFY modelsChanged FINAL)
-    Q_PROPERTY(QString ollamaUrl READ ollamaUrl WRITE setOllamaUrl NOTIFY ollamaUrlChanged FINAL)
-    Q_PROPERTY(QString systemPrompt READ systemPrompt WRITE setSystemPrompt NOTIFY systemPromptChanged FINAL)
-
-public:
-    /**
-     * @brief Creates a KLLMInterface.
-     * @param parent The parent QObject.
-     */
-    explicit KLLMInterface(QObject *parent = nullptr);
-
-    /**
-     * @brief Creates a KLLMInterface with the url set to \a ollamaUrl.
-     * @param ollamaUrl The URL to the Ollama instance.
-     * @param parent The parent QObject.
-     */
-    explicit KLLMInterface(const QString &ollamaUrl, QObject *parent = nullptr);
-
-    /**
-     * @brief Creates a KLLMInterface with the url set to \a ollamaUrl.
-     * @param ollamaUrl The URL to the Ollama instance.
-     * @param parent The parent QObject.
-     */
-    explicit KLLMInterface(const QUrl &ollamaUrl, QObject *parent = nullptr);
-
-    /**
-     * @brief Check whether the interface is ready.
-     *
-     * You should not use the interface until ready() returns true. Failure to observe this rule may result in undefined behavior.
-     *
-     * If the interface encounters an error, ready() will return false. However, do not use ready() to indicate to the user that the interface is in an error
-     * state, as the interface could be in the process of making its initial connection. Instead, you should use hasError() to check for an error state.
-     * Additionally, you should connect to errorOccurred() to handle errors as they arise.
-     *
-     * @return Returns whether the interface is ready.
-     */
-    [[nodiscard]] bool ready() const;
-
-    /**
-     * @brief Check whether the interface is in an error state.
-     *
-     * After you handle an error from errorEmitted(), you should monitor this property. When it becomes \c false, you can safely resume operations.
-     *
-     * @return Returns whether the interface is in an error state.
-     */
-    [[nodiscard]] bool hasError() const;
-
-    /**
-     * @brief Retrieve a list of models supported by the LLM backend.
-     *
-     * When creating a KLLMRequest, you should choose a model from this list for the request. If you do not specify a model,
-     * the request will probably fail.
-     *
-     * @return Returns a QStringList containing all valid models for this interface.
-     */
-    [[nodiscard]] QStringList models() const;
-
-    /**
-     * @brief Get the URL to the Ollama instance.
-     * @return The URL for the Ollama instance.
-     */
-    [[nodiscard]] QString ollamaUrl() const;
-
-    /**
-     * @brief Set the URL to the Ollama instance.
-     *
-     * Since Ollama is a self-hostable service, users may wish to use different instances. Use this function to set the URL to the desired instance. It should
-     * \a not contain the \c /api portion of the URL.
-     *
-     * @param ollamaUrl The new URL for the Ollama instance.
-     */
-    void setOllamaUrl(const QString &ollamaUrl);
-
-    /**
-     * @brief A convenience overload of setOllamaUrl() that takes a QUrl.
-     * @param ollamaUrl The new URL for the Ollama instance.
-     */
-    void setOllamaUrl(const QUrl &ollamaUrl);
-
-    /**
-     * @brief Get the system prompt for the LLM.
-     * @return The system prompt string.
-     */
-    [[nodiscard]] QString systemPrompt() const;
-
-    /**
-     * @brief Set the system prompt for the LLM.
-     *
-     * LLMs can take system prompts that instruct them on how they should generally behave in a conversation. This could be anything from how they speak to what
-     * types of information they prefer to present. You can set a system prompt here to better cater to your users.
-     *
-     * @param systemPrompt The system prompt for the LLM.
-     */
-    void setSystemPrompt(const QString &systemPrompt);
-
-public Q_SLOTS:
-    /**
-     * @brief Request a completion from the LLM.
-     *
-     * Calling this function starts a request to the LLM backend. You should use the returned KLLMReply pointer to track the
-     * status of the LLM's response. Once the KLLMReply emits KLLMReply::finished(), it is your responsibility to either
-     * track or delete the KLLMReply; auto-deleting is not implemented yet.
-     *
-     * @param request The request object that will be used to create the actual LLM request.
-     * @return Returns a pointer to a KLLMReply that can be used to track the progress of the reply.
-     */
-    KLLMReply *getCompletion(const KLLMRequest &request);
-
-    /**
-     * @brief Request model info from Ollama.
-     *
-     * Calling this function starts a request to the LLM backend. You should use the returned KLLMReply pointer to track the
-     * status of the LLM's response. Once the KLLMReply emits KLLMReply::finished(), it is your responsibility to either
-     * track or delete the KLLMReply; auto-deleting is not implemented yet.
-     *
-     * @param request The request object that will be used to create the actual LLM request.
-     * @return Returns a pointer to a KLLMReply that can be used to track the progress of the reply.
-     */
-    KLLMReply *getModelInfo(const KLLMRequest &request);
-
-    /**
-     * @brief Reload the LLM interface.
-     *
-     * Reloading the interface can be used to check if a network error is gone or to see if the available models have changed.
-     */
-    void reload();
-
-Q_SIGNALS:
-    /**
-     * @brief This signal is emitted when any completion requested by the interface is completed.
-     * @param replyText Contains the text of the completion.
-     */
-    void finished(const QString &replyText);
-
-    void readyChanged();
-    void hasErrorChanged();
-    void modelsChanged();
-    void ollamaUrlChanged();
-    void systemPromptChanged();
-
-    /**
-     * @brief An error occurred while communicating with the interface.
-     * @param message Contains the human readable error message.
-     */
-    void errorOccurred(const QString &message);
-
-private:
-    QNetworkAccessManager *const m_manager;
-    QStringList m_models;
-    bool m_ready = false;
-    bool m_hasError = false;
-    QString m_ollamaUrl;
-    QString m_systemPrompt;
-    QMetaObject::Connection m_ollamaCheck;
-};
-}
diff --git a/src/kllm/KLLMReply.cpp b/src/kllm/KLLMReply.cpp
deleted file mode 100644
index e2e01c8..0000000
--- a/src/kllm/KLLMReply.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#include "KLLMReply.h"
-#include "kllmcore_debug.h"
-
-#include <QNetworkReply>
-
-using namespace Qt::StringLiterals;
-using namespace KLLMCore;
-
-KLLMReply::KLLMReply(QNetworkReply *netReply, QObject *parent, RequestTypes requestType)
-    : QObject{parent}
-    , m_reply{netReply}
-    , m_requestType{requestType}
-{
-    connect(m_reply, &QNetworkReply::finished, m_reply, [this] {
-        // Normally, we could assume that the tokens will never be empty once the request finishes, but it could be possible
-        // that the request failed and we have no tokens to parse.
-        if (m_requestType == RequestTypes::StreamingGenerate && !m_tokens.empty()) {
-            const auto finalResponse = m_tokens.constLast();
-            m_context.setOllamaContext(finalResponse["context"_L1].toArray());
-            m_info.totalDuration = std::chrono::nanoseconds{finalResponse["total_duration"_L1].toVariant().toULongLong()};
-            m_info.loadDuration = std::chrono::nanoseconds{finalResponse["load_duration"_L1].toVariant().toULongLong()};
-            m_info.promptEvalTokenCount = finalResponse["prompt_eval_count"_L1].toVariant().toULongLong();
-            m_info.promptEvalDuration = std::chrono::nanoseconds{finalResponse["prompt_eval_duration"_L1].toVariant().toULongLong()};
-            m_info.tokenCount = finalResponse["eval_count"_L1].toVariant().toULongLong();
-            m_info.duration = std::chrono::nanoseconds{finalResponse["eval_duration"_L1].toVariant().toULongLong()};
-        }
-
-        qCDebug(KLLMCORE_LOG) << "Ollama response finished";
-        m_finished = true;
-        Q_EMIT finished();
-    });
-    connect(m_reply, &QNetworkReply::errorOccurred, m_reply, [](QNetworkReply::NetworkError e) {
-        qCDebug(KLLMCORE_LOG) << "Ollama HTTP error:" << e;
-    });
-    connect(m_reply, &QNetworkReply::downloadProgress, m_reply, [this](qint64 received, qint64 /*total*/) {
-        m_incompleteTokens += m_reply->read(received - m_receivedSize);
-        m_receivedSize = received;
-        switch (m_requestType) {
-        case RequestTypes::Show:
-            m_tokens.append(QJsonDocument::fromJson(m_incompleteTokens));
-            break;
-        case RequestTypes::StreamingGenerate:
-            auto completeTokens = m_incompleteTokens.split('\n');
-            if (completeTokens.size() <= 1) {
-                return;
-            }
-            m_incompleteTokens = completeTokens.last();
-            completeTokens.removeLast();
-
-            m_tokens.reserve(completeTokens.count());
-            for (const auto &tok : std::as_const(completeTokens)) {
-                m_tokens.append(QJsonDocument::fromJson(tok));
-            }
-            break;
-        }
-        Q_EMIT contentAdded();
-    });
-}
-
-QString KLLMReply::readResponse() const
-{
-    QString ret;
-    switch (m_requestType) {
-    case RequestTypes::Show:
-        ret += QString::fromLatin1("## Template: \n```\n") + m_tokens.constFirst()["template"_L1].toString() + QString::fromLatin1("\n```\n");
-        ret += QString::fromLatin1("## Modelfile: \n```\n") + m_tokens.constFirst()["modelfile"_L1].toString() + QString::fromLatin1("\n```\n");
-        ret += QString::fromLatin1("## Parameters: \n```\n") + m_tokens.constFirst()["parameters"_L1].toString() + QString::fromLatin1("\n```\n");
-        ret += QString::fromLatin1("## Details: \n```\n")
-            + QString::fromLatin1(QJsonDocument::fromVariant(m_tokens.constFirst()["details"_L1].toVariant()).toJson()) + QString::fromLatin1("\n```\n");
-        ret += QString::fromLatin1("## Model Info: \n```\n")
-            + QString::fromLatin1(QJsonDocument::fromVariant(m_tokens.constFirst()["model_info"_L1].toVariant()).toJson()) + QString::fromLatin1("\n```\n");
-        break;
-    case RequestTypes::StreamingGenerate:
-        for (const auto &tok : m_tokens)
-            ret += tok["response"_L1].toString();
-        break;
-    }
-
-    return ret;
-}
-
-const KLLMContext &KLLMReply::context() const
-{
-    return m_context;
-}
-
-const KLLMReplyInfo &KLLMReply::info() const
-{
-    return m_info;
-}
-
-const KLLMReply::RequestTypes &KLLMReply::requestType() const
-{
-    return m_requestType;
-}
-
-bool KLLMReply::isFinished() const
-{
-    return m_finished;
-}
-#include "moc_KLLMReply.cpp"
diff --git a/src/kllm/KLLMReply.h b/src/kllm/KLLMReply.h
deleted file mode 100644
index be4cbc9..0000000
--- a/src/kllm/KLLMReply.h
+++ /dev/null
@@ -1,154 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include "kllmcore_export.h"
-#include <QJsonDocument>
-
-#include "KLLMContext.h"
-
-class QNetworkReply;
-namespace KLLMCore
-{
-/**
- * @brief The KLLMReplyInfo class represents information about a reply from an LLM.
- *
- * When an LLM generates a completion, the server generally will return some information about the completion, including the
- * duration of the completion, the number of tokens received, and the duration of the prompt evaluation. This struct encapsulates such information.
- * If any one of these fields is not available, it will be set to its default value.
- */
-struct KLLMCORE_EXPORT KLLMReplyInfo {
-    //! The total time from when the request was received by the server to when the reply was returned.
-    std::chrono::nanoseconds totalDuration;
-
-    //! The time spent loading the model.
-    std::chrono::nanoseconds loadDuration;
-
-    //! The number of tokens in the prompt.
-    int promptEvalTokenCount;
-
-    //! The time spent evaluating the prompt.
-    std::chrono::nanoseconds promptEvalDuration;
-
-    //! The number of tokens in the reply.
-    int tokenCount;
-
-    //! The time spent generating the reply.
-    std::chrono::nanoseconds duration;
-};
-
-/**
- * @brief The KLLMReply class represents a reply from an LLM.
- *
- * Requesting a completion from a KLLMInterface will return a KLLMReply. You can use this to track the progress of the LLM's
- * reply.
- *
- * If you want to stream a reply as it is written in real time, connect to contentAdded() and use readResponse() to retrieve
- * the new content. If you prefer to wait for the entire reply before displaying anything, connect to finished(), which will
- * only be emitted once the reply is complete.
- */
-class KLLMCORE_EXPORT KLLMReply : public QObject
-{
-    Q_OBJECT
-
-public:
-    /**
-     * @brief Specifies the request type.
-     *
-     * When the class in instantiated the type of request should be specified
-     */
-    enum class RequestTypes {
-        StreamingGenerate,
-        Show
-    };
-    /**
-     * @brief Get the current response content.
-     *
-     * This function returns what it has recieved of the response so far. Therefore, until finished() is emitted, this
-     * function may return different values. However, once finished() is emitted, the content is guaranteed to remain
-     * constant.
-     *
-     * @return The content that has been returned so far.
-     */
-    [[nodiscard]] QString readResponse() const;
-
-    /**
-     * @brief Get the context token for this response (if applicable).
-     *
-     * Messages sent by most LLMs have a context identifier that allows you to chain messages into a conversation. To create
-     * such a conversation, you need to take this context object and set it on the next KLLMRequest in the conversation.
-     * KLLMInterface::getCompletion() will use that context object to continue the message thread.
-     *
-     * @return A context object that refers to this response.
-     */
-    const KLLMContext &context() const;
-
-    /**
-     * @brief Get extra information about the reply (if applicable).
-     *
-     * This function returns a KLLMReplyInfo object containing information about this reply. If the reply has not finished, the KLLMReplyInfo object will have
-     * all members set to their default values.
-     *
-     * @return Extra information about the reply.
-     */
-    const KLLMReplyInfo &info() const;
-
-    /**
-     * @brief Check whether the reply has finished.
-     *
-     * If you need to know if the response has finished changing or if the context has been received yet, call this function.
-     *
-     * @return Whether the reply has finished.
-     */
-    [[nodiscard]] bool isFinished() const;
-
-    /**
-     * @brief Get request type.
-     *
-     * The request type is set when this object is created.
-     *
-     * @return Corresponding request type.
-     */
-    const RequestTypes &requestType() const;
-
-protected:
-    explicit KLLMReply(QNetworkReply *netReply, QObject *parent = nullptr, RequestTypes requestType = RequestTypes::StreamingGenerate);
-
-    friend class KLLMInterface;
-
-Q_SIGNALS:
-    /**
-     * @brief Emits when new content has been added to the response.
-     *
-     * If you are not streaming the response live, this signal is not of importance to you. However, if you are streaming
-     * content, when this signal is emitted, you should call readResponse() to update the response that your application
-     * shows.
-     */
-    void contentAdded();
-
-    /**
-     * @brief Emits when the LLM has finished returning its response.
-     *
-     * After this signal has emitted, the content is guaranteed to not change. At this point, you should call readResponse()
-     * to get the content and then either take ownership of the KLLMReply or delete it, as automatic reply deletion is not
-     * implemented yet.
-     */
-    void finished();
-
-private:
-    QNetworkReply *const m_reply;
-    QByteArray m_incompleteTokens;
-
-    QList<QJsonDocument> m_tokens;
-
-    KLLMContext m_context;
-    KLLMReplyInfo m_info;
-    RequestTypes m_requestType = RequestTypes::StreamingGenerate;
-
-    int m_receivedSize = 0;
-    bool m_finished = false;
-};
-}
diff --git a/src/kllm/KLLMRequest.cpp b/src/kllm/KLLMRequest.cpp
deleted file mode 100644
index 4391994..0000000
--- a/src/kllm/KLLMRequest.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#include "KLLMRequest.h"
-
-using namespace KLLMCore;
-KLLMRequest::KLLMRequest(const QString &message)
-    : m_message{message}
-{
-}
-
-QString KLLMRequest::message() const
-{
-    return m_message;
-}
-
-const KLLMContext &KLLMRequest::context() const
-{
-    return m_context;
-}
-
-QString KLLMRequest::model() const
-{
-    return m_model;
-}
-
-void KLLMRequest::setMessage(const QString &message)
-{
-    m_message = message;
-}
-
-void KLLMRequest::setContext(const KLLMContext &context)
-{
-    m_context = context;
-}
-
-void KLLMRequest::setModel(const QString &model)
-{
-    m_model = model;
-}
-
-QDebug operator<<(QDebug d, const KLLMRequest &t)
-{
-    d << "Model: " << t.model();
-    d << "Message: " << t.message();
-    d << "Context: " << t.context();
-    return d;
-}
diff --git a/src/kllm/KLLMRequest.h b/src/kllm/KLLMRequest.h
deleted file mode 100644
index 5b42089..0000000
--- a/src/kllm/KLLMRequest.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Loren Burkholder <computersemiexpert@outlook.com>
-// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
-//
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-
-#pragma once
-
-#include "kllmcore_export.h"
-
-#include "KLLMContext.h"
-#include <QDebug>
-namespace KLLMCore
-{
-/**
- * @brief The KLLMRequest class contains a representation of a message to an LLM.
- *
- * Before calling KLLMInterface::getCompletion(), you need to create a KLLMRequest with information about the desired
- * completion.
- */
-class KLLMCORE_EXPORT KLLMRequest
-{
-public:
-    /**
-     * @brief Creates a KLLMRequest with the message \a message.
-     * @param message The message that will be sent to the LLM.
-     * @param parent
-     */
-    explicit KLLMRequest(const QString &message);
-
-    /**
-     * @brief Use this to get the message for the LLM.
-     * @return Returns the message to be sent to the LLM.
-     */
-    [[nodiscard]] QString message() const;
-
-    /**
-     * @brief Use this to retrive the context for the LLM.
-     * @return Returns the context object to be sent to the LLM.
-     */
-    const KLLMContext &context() const;
-
-    /**
-     * @brief Gets the LLM model that will be used by the backend.
-     * @return The name of the model to be used for this request.
-     */
-    [[nodiscard]] QString model() const;
-
-    /**
-     * @brief Sets the message to be sent to the LLM.
-     * @param message The message text to be sent to the LLM.
-     */
-    void setMessage(const QString &message);
-
-    /**
-     * @brief Sets the conversation context for the LLM.
-     *
-     * If you want the LLM to respond in context of previous messages, you should set this to the context returned in the
-     * previous KLLMReply.
-     *
-     * @param context The context object for this request.
-     */
-    void setContext(const KLLMContext &context);
-
-    /**
-     * @brief Sets the model to be used for this request.
-     *
-     * You should set this to one of the values returned by KLLMInterface::models(); failure to do so will likely produce an
-     * error from the backend.
-     *
-     * @param model The name of the model to be used for this request.
-     */
-    void setModel(const QString &model);
-
-private:
-    QString m_message;
-    KLLMContext m_context;
-    QString m_model;
-};
-}
-KLLMCORE_EXPORT QDebug operator<<(QDebug d, const KLLMCore::KLLMRequest &t);
diff --git a/src/mcp/CMakeLists.txt b/src/mcp/CMakeLists.txt
new file mode 100644
index 0000000..f77707e
--- /dev/null
+++ b/src/mcp/CMakeLists.txt
@@ -0,0 +1,29 @@
+add_library(kom_mcp STATIC
+    KompanionQtServer.cpp
+)
+
+qt_add_resources(kom_mcp kompanion_mcp_resources
+    PREFIX "/kompanion"
+    BASE "."
+    FILES ToolSchemas.json
+)
+
+target_link_libraries(kom_mcp PRIVATE
+    kom_dal
+    kom_ai
+    KF6::ConfigCore
+    Qt6::Core
+    Qt6::Network
+    Qt6::McpServer
+    Qt6::McpCommon
+)
+
+target_compile_options(kom_mcp PRIVATE -fexceptions)
+
+target_compile_definitions(kom_mcp PRIVATE
+  PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
+  KOMPANION_DB_INIT_INSTALL_DIR="${KOMPANION_DB_INIT_INSTALL_DIR}"
+)
+
+
+install(FILES ToolSchemas.json DESTINATION ${KDE_INSTALL_DATADIR}/kompanion/mcp)
diff --git a/src/mcp/HandlersIntrospection.hpp b/src/mcp/HandlersIntrospection.hpp
index e5d02e3..9c8dd11 100644
--- a/src/mcp/HandlersIntrospection.hpp
+++ b/src/mcp/HandlersIntrospection.hpp
@@ -112,6 +112,11 @@ inline std::optional<std::string> currentDsnSource() {
 
 } // namespace detail
 
+// Echos back the request payload
+inline std::string echo(const std::string& reqJson) {
+    return reqJson;
+}
+
 // Produces a JSON response summarising project state: memory docs, task table, git status.
 inline std::string project_snapshot(const std::string& reqJson) {
     (void)reqJson;
diff --git a/src/mcp/HandlersMemory.hpp b/src/mcp/HandlersMemory.hpp
index c0da754..274549d 100644
--- a/src/mcp/HandlersMemory.hpp
+++ b/src/mcp/HandlersMemory.hpp
@@ -14,7 +14,7 @@
 #include <unordered_set>
 #include <vector>
 
-#include "dal/PgDal.hpp"
+#include "PgDal.hpp"
 
 namespace Handlers {
 namespace detail {
diff --git a/src/mcp/RegisterTools.hpp b/src/mcp/RegisterTools.hpp
index d4284ce..3e6a48e 100644
--- a/src/mcp/RegisterTools.hpp
+++ b/src/mcp/RegisterTools.hpp
@@ -5,6 +5,7 @@
 #include "HandlersMemory.hpp"
 
 inline void register_default_tools(KomMcpServer& server) {
+    server.registerTool("echo", Handlers::echo);
     server.registerTool("kom.memory.v1.save_context", Handlers::save_context);
     server.registerTool("kom.memory.v1.recall_context", Handlers::recall_context);
     server.registerTool("kom.memory.v1.embed_text", Handlers::embed_text);
diff --git a/src/mcp/Tools.hpp b/src/mcp/Tools.hpp
index 8f53366..02ef65b 100644
--- a/src/mcp/Tools.hpp
+++ b/src/mcp/Tools.hpp
@@ -18,6 +18,11 @@ inline std::string json_arr(const std::vector<std::string>& items) {
     os << "]"; return os.str();
 }
 
+// `echo` tool: echoes back the input
+inline std::string echo_response(const std::string& input) {
+    return input;
+}
+
 // `ping` tool: echoes { ok: true, tools: [...] }
 inline std::string ping_response(const std::vector<std::string>& toolNames) {
     std::vector<std::string> quoted; quoted.reserve(toolNames.size());
