chore: format llm c files
C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
JS/TS / lint (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 16s
C++ / clang-tidy (pull_request) Failing after 43s
Python / static (pull_request) Failing after 57s
Rust / fmt (pull_request) Successful in 1m30s
Rust / build (pull_request) Successful in 2m5s
Rust / clippy (pull_request) Successful in 1m55s
Python / verify (pull_request) Successful in 2m23s

This commit is contained in:
2026-08-31 19:02:26 +02:00
parent dbb51ceadd
commit 52feb6006a
27 changed files with 741 additions and 1190 deletions
+69 -91
View File
@@ -32,12 +32,9 @@ WebFetchTool::WebFetchTool(QObject* parent) : LlmTool(parent) {}
WebFetchTool::~WebFetchTool() {
for (auto* job : m_jobs) {
if (job->timer)
job->timer->stop();
if (job->reply)
job->reply->abort();
if (job->timer) job->timer->stop();
if (job->reply) job->reply->abort();
}
// Pending result callbacks are dropped; the client is going away.
qDeleteAll(m_jobs);
}
@@ -64,9 +61,9 @@ QJsonObject WebFetchTool::parameters() const {
QJsonObject format;
format[QStringLiteral("type")] = QStringLiteral("string");
format[QStringLiteral("enum")] = formats;
format[QStringLiteral("description")] =
QStringLiteral("The format to return the content in. Defaults to "
"text.");
format[QStringLiteral("description")] = QStringLiteral(
"The format to return the content in. Defaults to "
"text.");
QJsonObject timeout;
timeout[QStringLiteral("type")] = QStringLiteral("integer");
@@ -90,18 +87,14 @@ QJsonObject WebFetchTool::parameters() const {
}
void WebFetchTool::completeJob(Job* job, QJsonObject result) {
// Deliver on a later event loop iteration; LlmClient relies on tool
// results never arriving synchronously within execute().
QMetaObject::invokeMethod(
this,
[this, job, result = std::move(result)]() mutable {
if (!m_jobs.contains(job))
return;
if (!m_jobs.contains(job)) return;
auto done = std::move(job->done);
m_jobs.removeAll(job);
job->timer->deleteLater();
if (job->reply)
job->reply->deleteLater();
if (job->reply) job->reply->deleteLater();
delete job;
done(std::move(result));
},
@@ -126,7 +119,7 @@ void WebFetchTool::execute(
return;
}
if (url.scheme() != QLatin1String("http") &&
url.scheme() != QLatin1String("https")) {
url.scheme() != QLatin1String("https")) {
fail(QStringLiteral("URL must use http:// or https://"));
return;
}
@@ -136,20 +129,18 @@ void WebFetchTool::execute(
if (job->format != QLatin1String("html"))
job->format = QStringLiteral("text");
const int timeoutMs = qBound(
1,
args[QStringLiteral("timeout")].toInt(
DefaultTimeoutSeconds),
MaxTimeoutSeconds) *
1000;
const int timeoutMs =
qBound(
1,
args[QStringLiteral("timeout")].toInt(DefaultTimeoutSeconds),
MaxTimeoutSeconds) *
1000;
job->timer = new QTimer(this);
job->timer->setSingleShot(true);
connect(
job->timer, &QTimer::timeout, this, [this, job]() {
if (job->reply)
job->reply->abort();
});
connect(job->timer, &QTimer::timeout, this, [this, job]() {
if (job->reply) job->reply->abort();
});
job->timer->start(timeoutMs);
QNetworkRequest request(url);
@@ -159,13 +150,12 @@ void WebFetchTool::execute(
job->format == QLatin1String("html")
? "text/html;q=1.0, application/xhtml+xml;q=0.9, */*;q=0.1"
: "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, "
"*/*;q=0.1");
"*/*;q=0.1");
request.setRawHeader("Accept-Language", "en-US,en;q=0.9");
job->reply = m_manager.get(request);
connect(job->reply, &QNetworkReply::readyRead, this, [this, job]() {
if (!job->reply)
return;
if (!job->reply) return;
job->body += job->reply->readAll();
if (job->body.size() > MaxResponseBytes) {
job->tooLarge = true;
@@ -176,8 +166,7 @@ void WebFetchTool::execute(
QNetworkReply* reply = job->reply;
job->reply = nullptr;
job->timer->stop();
if (!reply)
return;
if (!reply) return;
const QNetworkReply::NetworkError error = reply->error();
const QString errorString = reply->errorString();
@@ -187,13 +176,14 @@ void WebFetchTool::execute(
const QByteArray contentType =
reply->rawHeader("Content-Type").toLower();
const int status =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)
.toInt();
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (job->tooLarge) {
completeJob(job, makeError(
QStringLiteral("Response too large (exceeds the 5 MB "
"limit")));
completeJob(
job,
makeError(QStringLiteral(
"Response too large (exceeds the 5 MB "
"limit")));
return;
}
if (error != QNetworkReply::NoError) {
@@ -207,25 +197,23 @@ void WebFetchTool::execute(
completeJob(
job,
makeError(
QStringLiteral("Server returned status %1")
.arg(status)));
QStringLiteral("Server returned status %1").arg(status)));
return;
}
const QString mime =
QString::fromLatin1(contentType).section(QLatin1Char(';'), 0, 0)
.trimmed();
const QString mime = QString::fromLatin1(contentType)
.section(QLatin1Char(';'), 0, 0)
.trimmed();
if (mime.startsWith(QLatin1String("image/"))) {
completeJob(
job,
makeError(
QStringLiteral(
"Unsupported fetched image content type: %1")
QStringLiteral("Unsupported fetched image content type: %1")
.arg(mime)));
return;
}
const bool textual = mime.isEmpty() ||
mime.startsWith(QLatin1String("text/")) ||
const bool textual =
mime.isEmpty() || mime.startsWith(QLatin1String("text/")) ||
mime == QLatin1String("application/json") ||
mime.endsWith(QLatin1String("+json")) ||
mime == QLatin1String("application/xml") ||
@@ -236,19 +224,18 @@ void WebFetchTool::execute(
completeJob(
job,
makeError(
QStringLiteral(
"Unsupported fetched file content type: %1")
QStringLiteral("Unsupported fetched file content type: %1")
.arg(mime)));
return;
}
QString content = QString::fromUtf8(body);
if (mime.contains(QLatin1String("text/html")) &&
job->format == QLatin1String("text"))
job->format == QLatin1String("text"))
content = extractTextFromHtml(content);
if (content.size() > MaxOutputChars)
content = content.left(MaxOutputChars) +
QStringLiteral("\n[... truncated ...]");
QStringLiteral("\n[... truncated ...]");
completeJob(job, makeOutput(content));
});
}
@@ -256,14 +243,12 @@ void WebFetchTool::execute(
void WebFetchTool::cancel() {
for (auto* job : m_jobs) {
job->timer->stop();
if (job->reply)
job->reply->abort();
if (job->reply) job->reply->abort();
}
}
QString WebFetchTool::decodeEntities(const QString& text) {
if (!text.contains(QLatin1Char('&')))
return text;
if (!text.contains(QLatin1Char('&'))) return text;
QString out;
out.reserve(text.size());
for (qsizetype i = 0; i < text.size(); ++i) {
@@ -292,10 +277,11 @@ QString WebFetchTool::decodeEntities(const QString& text) {
replacement = QLatin1Char(' ');
else {
bool ok = false;
const quint32 codePoint = entity.startsWith(QLatin1String("#x")) ||
entity.startsWith(QLatin1String("#X"))
? entity.mid(2).toUInt(&ok, 16)
: entity.toUInt(&ok);
const quint32 codePoint =
entity.startsWith(QLatin1String("#x")) ||
entity.startsWith(QLatin1String("#X"))
? entity.mid(2).toUInt(&ok, 16)
: entity.toUInt(&ok);
if (ok && codePoint != 0) {
const char32_t ucs4[2] = {
static_cast<char32_t>(codePoint),
@@ -326,12 +312,18 @@ QString WebFetchTool::extractTextFromHtml(const QString& html) {
QStringLiteral("style"),
};
static const QSet<QString> kVoidTags = {
QStringLiteral("area"), QStringLiteral("base"),
QStringLiteral("br"), QStringLiteral("col"),
QStringLiteral("embed"), QStringLiteral("hr"),
QStringLiteral("img"), QStringLiteral("input"),
QStringLiteral("link"), QStringLiteral("meta"),
QStringLiteral("source"), QStringLiteral("track"),
QStringLiteral("area"),
QStringLiteral("base"),
QStringLiteral("br"),
QStringLiteral("col"),
QStringLiteral("embed"),
QStringLiteral("hr"),
QStringLiteral("img"),
QStringLiteral("input"),
QStringLiteral("link"),
QStringLiteral("meta"),
QStringLiteral("source"),
QStringLiteral("track"),
QStringLiteral("wbr"),
};
@@ -342,55 +334,43 @@ QString WebFetchTool::extractTextFromHtml(const QString& html) {
while (i < html.size()) {
const qsizetype open = html.indexOf(QLatin1Char('<'), i);
if (open < 0) {
if (skipDepth == 0)
text += html.mid(i);
if (skipDepth == 0) text += html.mid(i);
break;
}
if (skipDepth == 0)
text += html.mid(i, open - i);
if (skipDepth == 0) text += html.mid(i, open - i);
const qsizetype close = html.indexOf(QLatin1Char('>'), open);
if (close < 0)
break;
if (close < 0) break;
const QString tag =
html.mid(open + 1, close - open - 1).trimmed().toLower();
i = close + 1;
if (tag.startsWith(QLatin1Char('!')) ||
tag.startsWith(QLatin1Char('?')))
tag.startsWith(QLatin1Char('?')))
continue;
QString name = tag;
if (name.startsWith(QLatin1Char('/'))) {
if (skipDepth > 0)
--skipDepth;
if (skipDepth > 0) --skipDepth;
continue;
}
qsizetype j = 0;
while (j < name.size() &&
(name.at(j).isLetterOrNumber() ||
name.at(j) == QLatin1Char(':') ||
name.at(j) == QLatin1Char('-')))
while (j < name.size() && (name.at(j).isLetterOrNumber() ||
name.at(j) == QLatin1Char(':') ||
name.at(j) == QLatin1Char('-')))
++j;
name = name.left(j);
if (kRawTags.contains(name)) {
// Raw-text element: swallow everything up to its close tag.
const qsizetype rawEnd =
html.indexOf(QStringLiteral("</") + name, i,
Qt::CaseInsensitive);
if (rawEnd < 0)
break;
const qsizetype rawEnd = html.indexOf(
QStringLiteral("</") + name, i, Qt::CaseInsensitive);
if (rawEnd < 0) break;
const qsizetype rawClose = html.indexOf(QLatin1Char('>'), rawEnd);
if (rawClose < 0)
break;
if (rawClose < 0) break;
i = rawClose + 1;
continue;
}
if (kVoidTags.contains(name))
continue;
if (kVoidTags.contains(name)) continue;
if (skipDepth > 0) {
// Browsers implicitly close <head> at <body>; malformed pages
// without a </head> would otherwise swallow the whole page.
if (name == QLatin1String("body")) {
skipDepth = 0;
continue;
@@ -402,7 +382,6 @@ QString WebFetchTool::extractTextFromHtml(const QString& html) {
++skipDepth;
continue;
}
// Normal tag: replace with a space so words do not merge.
text += QLatin1Char(' ');
}
@@ -411,8 +390,7 @@ QString WebFetchTool::extractTextFromHtml(const QString& html) {
for (const QString& line : out.split(QLatin1Char('\n'))) {
const QString flat = line.simplified();
if (flat.isEmpty()) {
if (!lines.isEmpty() && lines.last().isEmpty())
continue;
if (!lines.isEmpty() && lines.last().isEmpty()) continue;
lines.append(QString());
} else {
lines.append(flat);