| 75 | } |
| 76 | |
| 77 | QFuture<LLMQore::ToolResult> ExecuteTerminalCommandTool::executeAsync(const QJsonObject &input) |
| 78 | { |
| 79 | using LLMQore::ToolResult; |
| 80 | |
| 81 | QString command = input.value("command").toString().trimmed(); |
| 82 | QString args = input.value("args").toString().trimmed(); |
| 83 | |
| 84 | if (command.isEmpty()) { |
| 85 | LOG_MESSAGE("ExecuteTerminalCommandTool: Command is empty"); |
| 86 | return QtFuture::makeReadyFuture(ToolResult::error("Error: Command parameter is required.")); |
| 87 | } |
| 88 | |
| 89 | // Tolerate models that pack the whole command line into `command`. As long as `args` is |
| 90 | // empty we can safely split on the first whitespace — the allowlist check still validates |
| 91 | // the actual executable name. |
| 92 | if (args.isEmpty()) { |
| 93 | const int firstSpace = command.indexOf(QRegularExpression("\\s")); |
| 94 | if (firstSpace > 0) { |
| 95 | args = command.mid(firstSpace + 1).trimmed(); |
| 96 | command = command.left(firstSpace); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | if (command.length() > MAX_COMMAND_LENGTH) { |
| 101 | LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command too long (%1 chars)") |
| 102 | .arg(command.length())); |
| 103 | return QtFuture::makeReadyFuture( |
| 104 | ToolResult::error(QString("Error: Command exceeds maximum length of %1 characters.") |
| 105 | .arg(MAX_COMMAND_LENGTH))); |
| 106 | } |
| 107 | |
| 108 | if (args.length() > MAX_ARGS_LENGTH) { |
| 109 | LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Arguments too long (%1 chars)") |
| 110 | .arg(args.length())); |
| 111 | return QtFuture::makeReadyFuture( |
| 112 | ToolResult::error(QString("Error: Arguments exceed maximum length of %1 characters.") |
| 113 | .arg(MAX_ARGS_LENGTH))); |
| 114 | } |
| 115 | |
| 116 | if (!isCommandAllowed(command)) { |
| 117 | LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' is not allowed") |
| 118 | .arg(command)); |
| 119 | const QStringList allowed = getAllowedCommands(); |
| 120 | const QString allowedList = allowed.isEmpty() ? "none" : allowed.join(", "); |
| 121 | return QtFuture::makeReadyFuture( |
| 122 | ToolResult::error(QString("Error: Command '%1' is not in the allowed list. Allowed commands: %2") |
| 123 | .arg(command) |
| 124 | .arg(allowedList))); |
| 125 | } |
| 126 | |
| 127 | if (!isCommandSafe(command)) { |
| 128 | LOG_MESSAGE(QString("ExecuteTerminalCommandTool: Command '%1' contains unsafe characters") |
| 129 | .arg(command)); |
| 130 | #ifdef Q_OS_WIN |
| 131 | const QString allowedChars = "alphanumeric characters, hyphens, underscores, dots, colons, " |
| 132 | "backslashes, and forward slashes"; |
| 133 | #else |
| 134 | const QString allowedChars = "alphanumeric characters, hyphens, underscores, dots, and slashes"; |