diff options
| author | Vineet Kumar <git@vineetk.net> | 2026-05-24 15:42:02 -0400 |
|---|---|---|
| committer | Vineet Kumar <git@vineetk.net> | 2026-05-24 15:42:02 -0400 |
| commit | ecf082bd0a47da1436e4f8b482f6b92f10f0b924 (patch) | |
| tree | 0592743fa2be7567af1baf496100fe456cffd311 | |
| parent | 137bec6d0706cfc341f99b12da52bb6932508580 (diff) | |
add confirm predicate to bash tool
| -rw-r--r-- | llm-tools.el | 52 |
1 files changed, 31 insertions, 21 deletions
diff --git a/llm-tools.el b/llm-tools.el index bbae522..41f6c56 100644 --- a/llm-tools.el +++ b/llm-tools.el | |||
| @@ -59,12 +59,7 @@ final file.")) | |||
| 59 | 59 | ||
| 60 | ;; TODO edit_file (hashline + patch apply) | 60 | ;; TODO edit_file (hashline + patch apply) |
| 61 | 61 | ||
| 62 | ;; TODO bash_readonly (shell-command-to-string, whitelisted commands) | ||
| 63 | ;; TODO compile command (calls build system, doesn't need confirmation, whitelisted commands) | ||
| 64 | |||
| 65 | ;; bash (make-process, taken from gptel-agent) | 62 | ;; bash (make-process, taken from gptel-agent) |
| 66 | ;; TODO refuse destructive operations like rm and dd | ||
| 67 | ;; TODO refuse operations that have dedicated tool definitions like grep and compile | ||
| 68 | (defun llm-tools--execute-bash (callback command) | 63 | (defun llm-tools--execute-bash (callback command) |
| 69 | "Execute COMMAND asynchronously in bash and call CALLBACK with output. | 64 | "Execute COMMAND asynchronously in bash and call CALLBACK with output. |
| 70 | 65 | ||
| @@ -90,6 +85,24 @@ COMMAND is the bash command string to execute." | |||
| 90 | exit-code output))))))))) | 85 | exit-code output))))))))) |
| 91 | proc)) | 86 | proc)) |
| 92 | 87 | ||
| 88 | (defun llm-tools--bash-confirm-p (command) | ||
| 89 | (let ((destructive | ||
| 90 | '("\\brm\\b" | ||
| 91 | "\\bdd\\b" | ||
| 92 | "\\bshred\\b" | ||
| 93 | "\\btruncate\\b" | ||
| 94 | "\\bmkfs\\b" | ||
| 95 | "\\bsudo\\b" | ||
| 96 | "\\bdoas\\b" | ||
| 97 | "[^>]>[^>]" ; any singular > that isn't >> | ||
| 98 | "\\$(" | ||
| 99 | "`" | ||
| 100 | "|[ \t]*\\bsh\\b" | ||
| 101 | "|[ \t]*\\bbash\\b"))) | ||
| 102 | (cl-some (lambda (regexp) | ||
| 103 | (string-match-p regexp command)) | ||
| 104 | destructive))) | ||
| 105 | |||
| 93 | (gptel-make-tool | 106 | (gptel-make-tool |
| 94 | :name "bash" | 107 | :name "bash" |
| 95 | :function #'llm-tools--execute-bash | 108 | :function #'llm-tools--execute-bash |
| @@ -99,10 +112,8 @@ This tool provides access to a Bash shell with GNU coreutils (or | |||
| 99 | equivalents) available. Use this to inspect system state, run builds, | 112 | equivalents) available. Use this to inspect system state, run builds, |
| 100 | tests or other development or system administration tasks. | 113 | tests or other development or system administration tasks. |
| 101 | 114 | ||
| 102 | TODO replace with saying do not use bash for operations that have equivalent tool definitions | 115 | Use the dedicated tools for file operations, searching, and code |
| 103 | Do NOT use this for file operations, finding, reading or editing files. | 116 | navigation instead of shell commands. |
| 104 | Use the provided file tools instead: `read_file`, `write_file`, | ||
| 105 | `edit_file`, `find_file`, `grep`. | ||
| 106 | 117 | ||
| 107 | - Quote file paths with spaces using double quotes. | 118 | - Quote file paths with spaces using double quotes. |
| 108 | - Chain dependent commands with && (or ; if failures are OK) | 119 | - Chain dependent commands with && (or ; if failures are OK) |
| @@ -121,18 +132,18 @@ returned as a string. Long outputs should be filtered/limited using pipes." | |||
| 121 | :description "The Bash command to execute. \ | 132 | :description "The Bash command to execute. \ |
| 122 | Can include pipes and standard shell operators. | 133 | Can include pipes and standard shell operators. |
| 123 | Example: 'ls -la | head -20' or 'grep -i error app.log | tail -50'")) | 134 | Example: 'ls -la | head -20' or 'grep -i error app.log | tail -50'")) |
| 124 | :confirm t | 135 | :confirm #'llm-tools--bash-confirm-p |
| 125 | :include t | 136 | :include t |
| 126 | :async t) | 137 | :async t) |
| 127 | 138 | ||
| 128 | ;; find_files (directory-files-recursively) | 139 | ;; find_files (directory-files-recursively) |
| 129 | (gptel-make-tool | 140 | (gptel-make-tool |
| 130 | :name "find_files" | 141 | :name "find_files" |
| 131 | :function (lambda (dir &optional pattern) | 142 | :function (lambda (dir &optional regexp) |
| 132 | (let ((git-dir (concat (file-name-as-directory dir) ".git"))) | 143 | (let ((git-dir (concat (file-name-as-directory dir) ".git"))) |
| 133 | (delq nil (mapcar (lambda (f) | 144 | (delq nil (mapcar (lambda (f) |
| 134 | (unless (string-prefix-p git-dir f) f)) | 145 | (unless (string-prefix-p git-dir f) f)) |
| 135 | (directory-files-recursively dir (or pattern "")))))) | 146 | (directory-files-recursively dir (or regexp "")))))) |
| 136 | :description "\ | 147 | :description "\ |
| 137 | Recursively find all files in a given directory, excluding files within | 148 | Recursively find all files in a given directory, excluding files within |
| 138 | .git directories. Returns a list of absolute file paths. Optionally | 149 | .git directories. Returns a list of absolute file paths. Optionally |
| @@ -142,9 +153,9 @@ or editing them." | |||
| 142 | :args '((:name "directory" | 153 | :args '((:name "directory" |
| 143 | :type string | 154 | :type string |
| 144 | :description "Path to the directory to be read, relative to the working directory.") | 155 | :description "Path to the directory to be read, relative to the working directory.") |
| 145 | (:name "pattern" | 156 | (:name "regexp" |
| 146 | :type string | 157 | :type string |
| 147 | :description "Optional file name pattern to filter results, e.g. '*.el' or '*.txt'." | 158 | :description "Optional regular expression pattern to search for, uses extended regex syntax." |
| 148 | :optional t)) | 159 | :optional t)) |
| 149 | :async nil | 160 | :async nil |
| 150 | :confirm nil | 161 | :confirm nil |
| @@ -153,25 +164,24 @@ or editing them." | |||
| 153 | ;; grep (just grep (or git grep) :D) | 164 | ;; grep (just grep (or git grep) :D) |
| 154 | (gptel-make-tool | 165 | (gptel-make-tool |
| 155 | :name "grep" | 166 | :name "grep" |
| 156 | :function (lambda (regex &optional path) | 167 | :function (lambda (regexp &optional path) |
| 157 | (with-temp-buffer | 168 | (with-temp-buffer |
| 158 | (if (magit-gitdir) | 169 | (if (magit-gitdir) |
| 159 | (call-process "git" nil t nil | 170 | (call-process "git" nil t nil |
| 160 | "--no-pager" | ||
| 161 | "grep" | 171 | "grep" |
| 162 | "-rnEH" | 172 | "-rnEH" |
| 163 | regex | 173 | regexp |
| 164 | (or path ".")) | 174 | (or path ".")) |
| 165 | (call-process "grep" nil t nil | 175 | (call-process "grep" nil t nil |
| 166 | "-rnEH" | 176 | "-rnEH" |
| 167 | regex | 177 | regexp |
| 168 | (or path "."))) | 178 | (or path "."))) |
| 169 | (buffer-string))) | 179 | (buffer-string))) |
| 170 | :description "\ | 180 | :description "\ |
| 171 | Recursively search for lines matching a regex pattern in files under a | 181 | Recursively search for lines matching a regexp pattern in files under a |
| 172 | given path. Uses `git grep` when inside a Git repository, otherwise falls | 182 | given path. Uses `git grep` when inside a Git repository, otherwise falls |
| 173 | back to GNU grep. Both are invoked with flags -rnEH (recursive, line | 183 | back to GNU grep. Both are invoked with flags -rnEH (recursive, line |
| 174 | numbers, extended regex, always show filename). When using `git grep`, | 184 | numbers, extended regexp, always show filename). When using `git grep`, |
| 175 | only tracked files are searched. Returns raw output with one match per | 185 | only tracked files are searched. Returns raw output with one match per |
| 176 | line in the format FILE:LINE:CONTENT. Use this tool to find code | 186 | line in the format FILE:LINE:CONTENT. Use this tool to find code |
| 177 | patterns, locate function definitions, search for strings, or identify | 187 | patterns, locate function definitions, search for strings, or identify |
| @@ -179,7 +189,7 @@ files containing specific text before reading them. For simple file name | |||
| 179 | discovery, use `find_files` instead. For reading file contents, use | 189 | discovery, use `find_files` instead. For reading file contents, use |
| 180 | `read_file`. The path argument is relative to the working directory; | 190 | `read_file`. The path argument is relative to the working directory; |
| 181 | omit it to search from the current directory." | 191 | omit it to search from the current directory." |
| 182 | :args '((:name "regex" | 192 | :args '((:name "regexp" |
| 183 | :type string | 193 | :type string |
| 184 | :description "\ | 194 | :description "\ |
| 185 | Regular expression pattern to search for. Uses GNU grep extended syntax.") | 195 | Regular expression pattern to search for. Uses GNU grep extended syntax.") |
