chore: restructure llm plugin to be more coherent & fix various bugs in llm ui
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 30s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m35s
Rust / fmt (pull_request) Successful in 36s
Rust / clippy (pull_request) Successful in 54s

This commit is contained in:
2026-10-18 12:58:38 +02:00
parent fc81be922f
commit 1fe8a3a959
54 changed files with 3022 additions and 2559 deletions
+37 -42
View File
@@ -10,30 +10,16 @@
namespace ZShell::llm {
const QString WebFetchTool::StoragePath =
QStringLiteral("/tmp/zshell-llm/webfetch");
namespace {
const char* kUserAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
QJsonObject makeOutput(const QString& text) {
QJsonObject obj;
obj[QStringLiteral("output")] = text;
return obj;
}
QJsonObject makeError(const QString& message) {
QJsonObject obj;
obj[QStringLiteral("error")] = message;
return obj;
}
} // namespace
WebFetchTool::WebFetchTool(QObject* parent) : LlmTool(parent) {}
WebFetchTool::WebFetchTool(ToolRegistry* registry, QObject* parent)
: LlmTool(registry, parent) {}
WebFetchTool::~WebFetchTool() {
for (auto* job : m_jobs) {
@@ -52,9 +38,9 @@ QString WebFetchTool::description() const {
"Fetch content from an HTTP or HTTPS URL and return it as plain "
"text or raw HTML. HTML pages are reduced to their visible text "
"by default. Only a limited amount of content is returned inline; "
"the full output of every call is saved under "
"/tmp/zshell-llm/webfetch/, where it can be paged through with "
"the readfile tool. This tool is read-only.");
"the full output of every call is saved in the sandbox directory "
"of the conversation, where it can be paged through with the "
"readfile tool. This tool is read-only.");
}
QJsonObject WebFetchTool::parameters() const {
@@ -94,7 +80,7 @@ QJsonObject WebFetchTool::parameters() const {
return schema;
}
void WebFetchTool::completeJob(Job* job, QJsonObject result) {
void WebFetchTool::completeJob(Job* job, ToolResult result) {
QMetaObject::invokeMethod(
this,
[this, job, result = std::move(result)]() mutable {
@@ -110,19 +96,23 @@ void WebFetchTool::completeJob(Job* job, QJsonObject result) {
}
void WebFetchTool::execute(
const QString& toolCallId,
const QJsonObject& args,
int outputBudgetChars,
std::function<void(const QJsonObject&)> done) {
const ToolContext& context, const QJsonObject& args, ToolDone done) {
auto* job = new Job;
job->done = std::move(done);
job->toolCallId = toolCallId;
job->budgetChars = outputBudgetChars;
job->toolCallId = context.toolCallId;
job->budgetChars = context.outputBudgetChars > 0
? context.outputBudgetChars
: registry()->outputBudgetChars(1);
job->storagePath =
context.workspace.isEmpty()
? QDir(QDir::tempPath())
.filePath(QStringLiteral("zshell-llm/webfetch"))
: context.workspace + QStringLiteral("/webfetch");
m_jobs.append(job);
auto fail = [this, job](const QString& message) {
job->timer->stop();
completeJob(job, makeError(message));
completeJob(job, {false, message});
};
const QString urlText = args[QStringLiteral("url")].toString().trimmed();
@@ -194,23 +184,27 @@ void WebFetchTool::execute(
if (job->tooLarge) {
completeJob(
job,
makeError(QStringLiteral(
"Response too large (exceeds the 5 MB "
"limit")));
ToolResult{
false,
QStringLiteral(
"Response too large (exceeds the 5 MB "
"limit")});
return;
}
if (error != QNetworkReply::NoError) {
completeJob(
job,
makeError(
QStringLiteral("Request failed: %1").arg(errorString)));
ToolResult{
false,
QStringLiteral("Request failed: %1").arg(errorString)});
return;
}
if (status < 200 || status >= 300) {
completeJob(
job,
makeError(
QStringLiteral("Server returned status %1").arg(status)));
ToolResult{
false,
QStringLiteral("Server returned status %1").arg(status)});
return;
}
@@ -220,9 +214,10 @@ void WebFetchTool::execute(
if (mime.startsWith(QLatin1String("image/"))) {
completeJob(
job,
makeError(
ToolResult{
false,
QStringLiteral("Unsupported fetched image content type: %1")
.arg(mime)));
.arg(mime)});
return;
}
const bool textual =
@@ -236,9 +231,10 @@ void WebFetchTool::execute(
if (!textual) {
completeJob(
job,
makeError(
ToolResult{
false,
QStringLiteral("Unsupported fetched file content type: %1")
.arg(mime)));
.arg(mime)});
return;
}
@@ -249,9 +245,8 @@ void WebFetchTool::execute(
const QString savedPath = saveToFile(*job, content, mime);
const int budget = job->budgetChars;
QString output = content;
int budget = inlineBudgetChars();
if (job->budgetChars > 0) budget = qMin(budget, job->budgetChars);
if (content.size() > budget) {
output = content.left(budget);
const QString note =
@@ -269,13 +264,13 @@ void WebFetchTool::execute(
". The remaining content is not "
"available.");
}
completeJob(job, makeOutput(output));
completeJob(job, ToolResult{true, output});
});
}
QString WebFetchTool::saveToFile(
const Job& job, const QString& content, const QString& mime) const {
QDir dir(StoragePath);
QDir dir(job.storagePath);
if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) return QString();
QString fileName;