fix: Add handling final argument for OpenAI responses tool calling

This commit is contained in:
Petr Mironychev
2026-05-21 14:19:16 +02:00
parent c4e34bb3d9
commit b33a1c2d43
6 changed files with 72 additions and 20 deletions

View File

@@ -77,10 +77,20 @@ QFuture<LLMQore::ToolResult> CreateNewFileTool::executeAsync(const QJsonObject &
if (!isInProject) {
const auto &settings = Settings::toolsSettings();
if (!settings.allowAccessOutsideProject()) {
const QString projectRoot = Context::ProjectUtils::getProjectRoot();
const QString hint = projectRoot.isEmpty()
? QStringLiteral(
"No project is currently open. Open a project in Qt Creator or "
"enable 'Allow file access outside project' in QodeAssist settings.")
: QString(
"Retry with a path under the active project root: '%1'. The build "
"directory is for compiler output only and cannot accept new source "
"files. If you really need to write outside the project, enable "
"'Allow file access outside project' in QodeAssist settings.")
.arg(projectRoot);
throw LLMQore::ToolRuntimeError(
QString("Error: File path '%1' is not within the current project. "
"Enable 'Allow file access outside project' in settings to create files outside project scope.")
.arg(absolutePath));
QString("Error: File path '%1' is not within the current project. %2")
.arg(absolutePath, hint));
}
LOG_MESSAGE(QString("Creating file outside project scope: %1").arg(absolutePath));
}

View File

@@ -143,10 +143,20 @@ QFuture<LLMQore::ToolResult> EditFileTool::executeAsync(const QJsonObject &input
if (!isInProject) {
const auto &settings = Settings::toolsSettings();
if (!settings.allowAccessOutsideProject()) {
const QString projectRoot = Context::ProjectUtils::getProjectRoot();
const QString hint = projectRoot.isEmpty()
? QStringLiteral(
"No project is currently open. Open a project in Qt Creator or "
"enable 'Allow file access outside project' in QodeAssist settings.")
: QString(
"Retry with a path under the active project root: '%1'. The build "
"directory is for compiler output only — source files must live under "
"the project root. If you really need to edit outside the project, "
"enable 'Allow file access outside project' in QodeAssist settings.")
.arg(projectRoot);
throw LLMQore::ToolRuntimeError(
QString("File path '%1' is not within the current project. "
"Enable 'Allow file access outside project' in settings to edit files outside the project.")
.arg(filePath));
QString("File path '%1' is not within the current project. %2")
.arg(filePath, hint));
}
LOG_MESSAGE(QString("Editing file outside project scope: %1").arg(filePath));
}

View File

@@ -13,6 +13,7 @@
#include <QProcess>
#include <QPromise>
#include <QRegularExpression>
#include <QRegularExpression>
#include <QSharedPointer>
#include <QTimer>
@@ -45,18 +46,26 @@ QJsonObject ExecuteTerminalCommandTool::parametersSchema() const
QJsonObject definition;
definition["type"] = "object";
const QString commandDesc = getCommandDescription();
const QStringList allowed = getAllowedCommands();
const QString allowedList = allowed.isEmpty() ? "none" : allowed.join(", ");
QJsonObject properties;
properties["command"] = QJsonObject{
{"type", "string"},
{"description", commandDesc}};
{"description",
QString("Name of the executable to run, WITHOUT any arguments or flags. "
"Must be exactly one of the allowed commands: %1. "
"Put every flag and argument in the separate `args` field. "
"Correct: command=\"ls\", args=\"-R\". "
"Incorrect: command=\"ls -R\" (the whole line in one field will be rejected).")
.arg(allowedList)}};
properties["args"] = QJsonObject{
{"type", "string"},
{"description",
"Optional arguments for the command. Arguments with spaces should be properly quoted. "
"Example: '--file \"path with spaces.txt\" --verbose'"}};
"Optional arguments and flags for the command, as a single string. Do NOT repeat the "
"command name here. Arguments with spaces should be quoted. "
"Example: args=\"--file \\\"path with spaces.txt\\\" --verbose\"."}};
definition["properties"] = properties;
definition["required"] = QJsonArray{"command"};
@@ -68,14 +77,25 @@ QFuture<LLMQore::ToolResult> ExecuteTerminalCommandTool::executeAsync(const QJso
{
using LLMQore::ToolResult;
const QString command = input.value("command").toString().trimmed();
const QString args = input.value("args").toString().trimmed();
QString command = input.value("command").toString().trimmed();
QString args = input.value("args").toString().trimmed();
if (command.isEmpty()) {
LOG_MESSAGE("ExecuteTerminalCommandTool: Command is empty");
return QtFuture::makeReadyFuture(ToolResult::error("Error: Command parameter is required."));
}
// Tolerate models that pack the whole command line into `command`. As long as `args` is
// empty we can safely split on the first whitespace — the allowlist check still validates
// the actual executable name.
if (args.isEmpty()) {
const int firstSpace = command.indexOf(QRegularExpression("\\s"));
if (firstSpace > 0) {
args = command.mid(firstSpace + 1).trimmed();
command = command.left(firstSpace);
}
}
if (command.length() > MAX_COMMAND_LENGTH) {
LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command too long (%1 chars)")
.arg(command.length()));