#pragma once #include "chatstore.hpp" #include "tool.hpp" #include "turn.hpp" #include #include #include #include #include #include #include #include #include class QJsonObject; class QNetworkReply; namespace ZShell::llm { class ChatGeneration; class ChatSession; class LlmClient : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(QString model READ model NOTIFY modelChanged) Q_PROPERTY( QStringList availableModels READ availableModels NOTIFY availableModelsChanged) Q_PROPERTY(int contextSize READ contextSize NOTIFY contextSizeChanged) Q_PROPERTY( bool toolsEnabled READ toolsEnabled WRITE setToolsEnabled NOTIFY toolsEnabledChanged) Q_PROPERTY( bool sandboxEnabled READ sandboxEnabled WRITE setSandboxEnabled NOTIFY sandboxEnabledChanged) Q_PROPERTY(QString serverError READ serverError NOTIFY serverErrorChanged) Q_PROPERTY(ZShell::llm::ChatStore* chats READ chats CONSTANT) public: explicit LlmClient(QObject* parent = nullptr); ~LlmClient() override; [[nodiscard]] ChatStore* chats() const { return m_store; } [[nodiscard]] bool busy() const { return m_busy; } [[nodiscard]] QString model() const { return m_model; } [[nodiscard]] QStringList availableModels() const { return m_availableModels; } [[nodiscard]] int contextSize() const { return m_tools->contextSize(); } [[nodiscard]] bool toolsEnabled() const { return m_tools->enabled(); } [[nodiscard]] bool sandboxEnabled() const { return m_tools->sandboxEnabled(); } [[nodiscard]] QString serverError() const { return m_serverError; } [[nodiscard]] ChatSession* streamingSession() const { return m_turn ? m_turn->session() : nullptr; } void setToolsEnabled(bool value); void setSandboxEnabled(bool value); Q_INVOKABLE void selectModel(const QString& id); Q_INVOKABLE void stop(); Q_INVOKABLE void refreshModels(); void startGeneration(ChatSession* session, ChatGeneration* target); void approveTools(ZShell::llm::ChatGeneration* generation); void denyTools(ZShell::llm::ChatGeneration* generation); void requestTitle(ChatSession* session, const QString& userText); void requestIcon(ChatSession* session, const QString& userText); Q_SIGNALS: void busyChanged(); void modelChanged(); void availableModelsChanged(); void contextSizeChanged(); void toolsEnabledChanged(); void sandboxEnabledChanged(); void serverErrorChanged(); void generationFailed( ZShell::llm::ChatSession* session, const QString& message); private: void sendRound(); void applyToolCallDelta(const QJsonObject& call); void roundFinished(); void executeAllCalls(); void flushCallResults(); void setApprovalPending(bool value); void finishPendingCalls(const QString& resultText); void finishTurn(); void endStream(); void fail(const QString& message); void handleLine(const QByteArray& line); void drainBuffer(); void updateTokenUsage(const QJsonObject& data); void setBusy(bool value); void setServerError(const QString& value); void setModel(const QString& value); void setContextSize(int size); void probeContextSize(std::function done = {}); void refreshAllStates(); void shortRequest( const QString& tag, const QString& systemPrompt, const QString& userText, std::function onResult); static QString completionsPath( const QString& endpoint, const QString& subpath); static QString jsonErrorMessage(const QByteArray& body); static QString serverErrorMessage( const QByteArray& body, const QString& fallback); [[nodiscard]] ToolContext toolContext() const; QNetworkAccessManager m_manager; ChatStore* m_store = nullptr; ToolRegistry* m_tools = nullptr; std::unique_ptr m_turn; QNetworkReply* m_reply = nullptr; QByteArray m_buffer; QTimer m_refreshTimer; QString m_endpoint; QString m_model; QStringList m_availableModels; QString m_serverError; double m_temperature = 0.7; bool m_busy = false; bool m_configEnabled = true; static constexpr int kMaxToolRounds = 12; }; } // namespace ZShell::llm