mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-07-23 11:41:03 -04:00
325 lines
11 KiB
C++
325 lines
11 KiB
C++
// Copyright (C) 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 <QStringList>
|
|
#include <QTimer>
|
|
|
|
#include <LLMQore/AcpTypes.hpp>
|
|
#include <LLMQore/RpcTransport.hpp>
|
|
|
|
namespace QodeAssist {
|
|
|
|
class FakeAcpAgent : public LLMQore::Rpc::Transport
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit FakeAcpAgent(QObject *parent = nullptr)
|
|
: LLMQore::Rpc::Transport(parent)
|
|
{}
|
|
|
|
void start() override
|
|
{
|
|
m_open = true;
|
|
setState(State::Connected);
|
|
}
|
|
|
|
void stop() override
|
|
{
|
|
if (!m_open)
|
|
return;
|
|
m_open = false;
|
|
setState(State::Disconnected);
|
|
emit closed();
|
|
}
|
|
|
|
bool isOpen() const override { return m_open; }
|
|
|
|
void send(const QJsonObject &message) override
|
|
{
|
|
const QString method = message.value("method").toString();
|
|
|
|
if (method.isEmpty() && message.contains("id")) {
|
|
m_permissionOutcomes.append(
|
|
message.value("result").toObject().value("outcome").toObject());
|
|
if (!m_hangOnPrompt)
|
|
finishPrompt();
|
|
return;
|
|
}
|
|
|
|
m_methods.append(method);
|
|
|
|
if (method == QLatin1String("initialize")) {
|
|
m_clientInfo = message.value("params").toObject().value("clientInfo").toObject();
|
|
reply(message, initializeResult());
|
|
} else if (method == QLatin1String("session/new")) {
|
|
m_newSessionCwd = message.value("params").toObject().value("cwd").toString();
|
|
m_offeredMcpServers = message.value("params").toObject().value("mcpServers").toArray();
|
|
if (m_hangOnNewSession)
|
|
return;
|
|
if (m_requireAuthentication && !m_authenticated) {
|
|
replyError(message, -32000, QStringLiteral("Authentication required"));
|
|
} else {
|
|
if (!m_commandsBeforeSessionReply.isEmpty()) {
|
|
postUpdate(
|
|
QJsonObject{
|
|
{"sessionUpdate", QStringLiteral("available_commands_update")},
|
|
{"availableCommands", m_commandsBeforeSessionReply}});
|
|
}
|
|
reply(message, QJsonObject{{"sessionId", m_sessionId}});
|
|
}
|
|
} else if (method == QLatin1String("session/load")) {
|
|
m_loadedSessionId = message.value("params").toObject().value("sessionId").toString();
|
|
if (m_loadSessionError.isEmpty())
|
|
reply(message, QJsonObject{{"sessionId", m_loadedSessionId}});
|
|
else
|
|
replyError(message, -32603, m_loadSessionError);
|
|
} else if (method == QLatin1String("authenticate")) {
|
|
m_authenticated = true;
|
|
m_authMethodUsed = message.value("params").toObject().value("methodId").toString();
|
|
reply(message, QJsonObject{});
|
|
} else if (method == QLatin1String("session/prompt")) {
|
|
m_prompts.append(message.value("params").toObject().value("prompt").toArray());
|
|
respondToPrompt(message);
|
|
} else if (method == QLatin1String("session/cancel")) {
|
|
m_cancelled = true;
|
|
}
|
|
}
|
|
|
|
void setRequireAuthentication(bool require) { m_requireAuthentication = require; }
|
|
void setSupportsLoadSession(bool supports) { m_loadSession = supports; }
|
|
void setSupportsEmbeddedContext(bool supports) { m_embeddedContext = supports; }
|
|
void setSupportsImages(bool supports) { m_images = supports; }
|
|
void setPromptFailure(const QString &error) { m_promptError = error; }
|
|
void setHangOnPrompt(bool hang) { m_hangOnPrompt = hang; }
|
|
void setReplyChunks(const QStringList &chunks) { m_chunks = chunks; }
|
|
void setThoughtChunks(const QStringList &chunks) { m_thoughts = chunks; }
|
|
|
|
void setPermissionRequest(const QJsonObject &toolCall, const QJsonArray &options)
|
|
{
|
|
m_permissionToolCall = toolCall;
|
|
m_permissionOptions = options;
|
|
}
|
|
|
|
void setFinishPromptWithoutWaiting(bool finish) { m_finishPromptWithoutWaiting = finish; }
|
|
|
|
void setToolCallUpdates(const QList<QJsonObject> &updates) { m_toolCalls = updates; }
|
|
void setPlanUpdates(const QList<QJsonArray> &plans) { m_plans = plans; }
|
|
void setAvailableCommandUpdates(const QList<QJsonArray> &updates)
|
|
{
|
|
m_availableCommandUpdates = updates;
|
|
}
|
|
void setCommandsBeforeSessionReply(const QJsonArray &commands)
|
|
{
|
|
m_commandsBeforeSessionReply = commands;
|
|
}
|
|
void setPromptUsage(const QJsonObject &usage) { m_promptUsage = usage; }
|
|
void setContextGauge(const QJsonObject &gauge) { m_contextGauge = gauge; }
|
|
void setSuggestedTitle(const QString &title) { m_suggestedTitle = title; }
|
|
void setLoadSessionError(const QString &error) { m_loadSessionError = error; }
|
|
void setHangOnNewSession(bool hang) { m_hangOnNewSession = hang; }
|
|
void setSupportsHttpMcp(bool supports) { m_httpMcp = supports; }
|
|
|
|
QJsonArray offeredMcpServers() const { return m_offeredMcpServers; }
|
|
|
|
QString loadedSessionId() const { return m_loadedSessionId; }
|
|
|
|
QList<QJsonObject> permissionOutcomes() const { return m_permissionOutcomes; }
|
|
|
|
QStringList methods() const { return m_methods; }
|
|
QString newSessionCwd() const { return m_newSessionCwd; }
|
|
QString authMethodUsed() const { return m_authMethodUsed; }
|
|
QJsonObject clientInfo() const { return m_clientInfo; }
|
|
QList<QJsonArray> prompts() const { return m_prompts; }
|
|
bool wasCancelled() const { return m_cancelled; }
|
|
|
|
private:
|
|
QJsonObject initializeResult() const
|
|
{
|
|
QJsonObject capabilities{
|
|
{"loadSession", m_loadSession},
|
|
{"promptCapabilities",
|
|
QJsonObject{{"image", m_images}, {"embeddedContext", m_embeddedContext}}},
|
|
{"mcpCapabilities", QJsonObject{{"http", m_httpMcp}, {"sse", false}}}};
|
|
QJsonObject result{{"protocolVersion", 1}, {"agentCapabilities", capabilities}};
|
|
result.insert(
|
|
"agentInfo",
|
|
QJsonObject{{"name", QStringLiteral("Fake")}, {"version", QStringLiteral("1.0")}});
|
|
if (m_requireAuthentication) {
|
|
result.insert(
|
|
"authMethods",
|
|
QJsonArray{
|
|
QJsonObject{{"id", QStringLiteral("login")}, {"name", QStringLiteral("Login")}}});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void respondToPrompt(const QJsonObject &request)
|
|
{
|
|
for (const QString &chunk : m_thoughts)
|
|
notifyUpdate(QStringLiteral("agent_thought_chunk"), chunk);
|
|
for (const QString &chunk : m_chunks)
|
|
notifyUpdate(QStringLiteral("agent_message_chunk"), chunk);
|
|
|
|
for (int i = 0; i < m_toolCalls.size(); ++i) {
|
|
QJsonObject update = m_toolCalls.at(i);
|
|
update.insert(
|
|
"sessionUpdate",
|
|
i == 0 ? QStringLiteral("tool_call") : QStringLiteral("tool_call_update"));
|
|
postUpdate(update);
|
|
}
|
|
|
|
for (const QJsonArray &entries : m_plans)
|
|
postUpdate(QJsonObject{{"sessionUpdate", QStringLiteral("plan")}, {"entries", entries}});
|
|
|
|
for (const QJsonArray &commands : m_availableCommandUpdates) {
|
|
postUpdate(
|
|
QJsonObject{
|
|
{"sessionUpdate", QStringLiteral("available_commands_update")},
|
|
{"availableCommands", commands}});
|
|
}
|
|
|
|
if (!m_contextGauge.isEmpty()) {
|
|
QJsonObject update = m_contextGauge;
|
|
update.insert("sessionUpdate", QStringLiteral("usage_update"));
|
|
postUpdate(update);
|
|
}
|
|
|
|
m_activePrompt = request;
|
|
|
|
if (!m_permissionToolCall.isEmpty()) {
|
|
askPermission();
|
|
if (!m_finishPromptWithoutWaiting)
|
|
return;
|
|
}
|
|
|
|
if (m_hangOnPrompt)
|
|
return;
|
|
|
|
finishPrompt();
|
|
}
|
|
|
|
void askPermission()
|
|
{
|
|
post(
|
|
QJsonObject{
|
|
{"jsonrpc", QStringLiteral("2.0")},
|
|
{"id", ++m_outgoingId},
|
|
{"method", QStringLiteral("session/request_permission")},
|
|
{"params",
|
|
QJsonObject{
|
|
{"sessionId", m_sessionId},
|
|
{"toolCall", m_permissionToolCall},
|
|
{"options", m_permissionOptions}}}});
|
|
}
|
|
|
|
void finishPrompt()
|
|
{
|
|
if (m_activePrompt.isEmpty())
|
|
return;
|
|
|
|
const QJsonObject request = m_activePrompt;
|
|
m_activePrompt = QJsonObject();
|
|
|
|
if (!m_promptError.isEmpty()) {
|
|
replyError(request, -32603, m_promptError);
|
|
return;
|
|
}
|
|
|
|
QJsonObject result{{"stopReason", QStringLiteral("end_turn")}};
|
|
if (!m_promptUsage.isEmpty())
|
|
result.insert("usage", m_promptUsage);
|
|
reply(request, result);
|
|
|
|
if (!m_suggestedTitle.isEmpty()) {
|
|
postUpdate(
|
|
QJsonObject{
|
|
{"sessionUpdate", QStringLiteral("session_info_update")},
|
|
{"title", m_suggestedTitle}});
|
|
}
|
|
}
|
|
|
|
void postUpdate(const QJsonObject &update)
|
|
{
|
|
post(
|
|
QJsonObject{
|
|
{"jsonrpc", QStringLiteral("2.0")},
|
|
{"method", QStringLiteral("session/update")},
|
|
{"params", QJsonObject{{"sessionId", m_sessionId}, {"update", update}}}});
|
|
}
|
|
|
|
void notifyUpdate(const QString &kind, const QString &text)
|
|
{
|
|
postUpdate(
|
|
QJsonObject{
|
|
{"sessionUpdate", kind},
|
|
{"content", QJsonObject{{"type", QStringLiteral("text")}, {"text", text}}}});
|
|
}
|
|
|
|
void reply(const QJsonObject &request, const QJsonObject &result)
|
|
{
|
|
post(
|
|
QJsonObject{
|
|
{"jsonrpc", QStringLiteral("2.0")},
|
|
{"id", request.value("id")},
|
|
{"result", result}});
|
|
}
|
|
|
|
void replyError(const QJsonObject &request, int code, const QString &message)
|
|
{
|
|
post(
|
|
QJsonObject{
|
|
{"jsonrpc", QStringLiteral("2.0")},
|
|
{"id", request.value("id")},
|
|
{"error", QJsonObject{{"code", code}, {"message", message}}}});
|
|
}
|
|
|
|
void post(const QJsonObject &message)
|
|
{
|
|
QTimer::singleShot(0, this, [this, message]() { emit messageReceived(message); });
|
|
}
|
|
|
|
bool m_open = false;
|
|
bool m_hangOnPrompt = false;
|
|
bool m_loadSession = false;
|
|
bool m_embeddedContext = false;
|
|
bool m_images = false;
|
|
bool m_requireAuthentication = false;
|
|
bool m_authenticated = false;
|
|
bool m_cancelled = false;
|
|
bool m_finishPromptWithoutWaiting = false;
|
|
bool m_hangOnNewSession = false;
|
|
bool m_httpMcp = false;
|
|
QJsonArray m_offeredMcpServers;
|
|
QString m_sessionId = QStringLiteral("fake-session");
|
|
QString m_promptError;
|
|
QString m_newSessionCwd;
|
|
QString m_authMethodUsed;
|
|
QJsonObject m_clientInfo;
|
|
int m_outgoingId = 1000;
|
|
QStringList m_chunks;
|
|
QStringList m_thoughts;
|
|
QStringList m_methods;
|
|
QList<QJsonArray> m_prompts;
|
|
QJsonObject m_activePrompt;
|
|
QJsonObject m_permissionToolCall;
|
|
QJsonArray m_permissionOptions;
|
|
QList<QJsonObject> m_permissionOutcomes;
|
|
QList<QJsonObject> m_toolCalls;
|
|
QList<QJsonArray> m_plans;
|
|
QList<QJsonArray> m_availableCommandUpdates;
|
|
QJsonArray m_commandsBeforeSessionReply;
|
|
QJsonObject m_promptUsage;
|
|
QJsonObject m_contextGauge;
|
|
QString m_suggestedTitle;
|
|
QString m_loadSessionError;
|
|
QString m_loadedSessionId;
|
|
};
|
|
|
|
} // namespace QodeAssist
|