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
137 lines
3.9 KiB
C++
137 lines
3.9 KiB
C++
#include "filetool.hpp"
|
|
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QJsonArray>
|
|
|
|
namespace ZShell::llm {
|
|
|
|
FileReadTool::FileReadTool(ToolRegistry* registry, QObject* parent)
|
|
: LlmTool(registry, parent) {}
|
|
|
|
QString FileReadTool::name() const {
|
|
return QStringLiteral("readfile");
|
|
}
|
|
|
|
QString FileReadTool::description() const {
|
|
return QStringLiteral(
|
|
"Read a text file from the local filesystem and return part of "
|
|
"its content. At most a limited number of characters is returned "
|
|
"per call; use offset and limit to page through larger files. "
|
|
"Binary files are not supported. Full output of webfetch calls "
|
|
"is saved in the sandbox directory and can be read this way. "
|
|
"This tool is read-only.");
|
|
}
|
|
|
|
QJsonObject FileReadTool::parameters() const {
|
|
QJsonObject path;
|
|
path[QStringLiteral("type")] = QStringLiteral("string");
|
|
path[QStringLiteral("description")] =
|
|
QStringLiteral("Path of the file to read");
|
|
|
|
QJsonObject offset;
|
|
offset[QStringLiteral("type")] = QStringLiteral("integer");
|
|
offset[QStringLiteral("minimum")] = 0;
|
|
offset[QStringLiteral("description")] =
|
|
QStringLiteral("Byte offset to start reading from. Defaults to 0.");
|
|
|
|
QJsonObject limit;
|
|
limit[QStringLiteral("type")] = QStringLiteral("integer");
|
|
limit[QStringLiteral("minimum")] = 1;
|
|
limit[QStringLiteral("description")] = QStringLiteral(
|
|
"Maximum number of characters to return. Defaults "
|
|
"to a value that fits the model's context window.");
|
|
|
|
QJsonObject properties;
|
|
properties[QStringLiteral("path")] = path;
|
|
properties[QStringLiteral("offset")] = offset;
|
|
properties[QStringLiteral("limit")] = limit;
|
|
|
|
QJsonObject schema;
|
|
schema[QStringLiteral("type")] = QStringLiteral("object");
|
|
schema[QStringLiteral("properties")] = properties;
|
|
QJsonArray required;
|
|
required.append(QStringLiteral("path"));
|
|
schema[QStringLiteral("required")] = required;
|
|
return schema;
|
|
}
|
|
|
|
void FileReadTool::execute(
|
|
const ToolContext& context, const QJsonObject& args, ToolDone done) {
|
|
const QString path = args[QStringLiteral("path")].toString().trimmed();
|
|
if (path.isEmpty()) {
|
|
done({false, QStringLiteral("Missing required argument 'path'")});
|
|
return;
|
|
}
|
|
const int offset = qMax(0, args[QStringLiteral("offset")].toInt(0));
|
|
const int budget = context.outputBudgetChars > 0
|
|
? context.outputBudgetChars
|
|
: registry()->outputBudgetChars(1);
|
|
const int limit =
|
|
qBound(1, args[QStringLiteral("limit")].toInt(budget), budget);
|
|
|
|
QFile file(path);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
const QFileInfo info(path);
|
|
if (info.exists() && info.isDir()) {
|
|
done(
|
|
{false,
|
|
QStringLiteral("Path is a directory, not a file: %1")
|
|
.arg(path)});
|
|
} else {
|
|
done({false, QStringLiteral("Cannot open file: %1").arg(path)});
|
|
}
|
|
return;
|
|
}
|
|
const qint64 fileSize = file.size();
|
|
if (fileSize == 0) {
|
|
file.close();
|
|
done({true, QString()});
|
|
return;
|
|
}
|
|
if (offset >= fileSize) {
|
|
file.close();
|
|
done(
|
|
{false,
|
|
QStringLiteral("Offset %1 is beyond the end of the file (%2 bytes)")
|
|
.arg(offset)
|
|
.arg(fileSize)});
|
|
return;
|
|
}
|
|
if (!file.seek(offset)) {
|
|
file.close();
|
|
done({false, QStringLiteral("Cannot seek in file: %1").arg(path)});
|
|
return;
|
|
}
|
|
const qint64 wanted = qint64(limit) * 4 + 16;
|
|
const QByteArray chunk = file.read(qMin(wanted, fileSize - offset));
|
|
file.close();
|
|
|
|
if (chunk.contains('\0')) {
|
|
done(
|
|
{false,
|
|
QStringLiteral("Binary files are not supported: %1").arg(path)});
|
|
return;
|
|
}
|
|
const QString content = QString::fromUtf8(chunk);
|
|
if (content.size() <= limit) {
|
|
done({true, content});
|
|
return;
|
|
}
|
|
const QString head = content.left(limit);
|
|
const int nextOffset = offset + head.toUtf8().size();
|
|
const QString output =
|
|
head +
|
|
QStringLiteral(
|
|
"\n\n[... truncated: showing %1 characters (bytes %2-%3 of a "
|
|
"%4 byte file). Continue with offset=%5.]")
|
|
.arg(limit)
|
|
.arg(offset)
|
|
.arg(nextOffset)
|
|
.arg(fileSize)
|
|
.arg(nextOffset);
|
|
done({true, output});
|
|
}
|
|
|
|
} // namespace ZShell::llm
|