Skip to content

Commit b70e46f

Browse files
committed
Add configurable shell for shell_command tool via toolCall.shellCommand.path and toolCall.shellCommand.args.
Fixes #370
1 parent 000fee2 commit b70e46f

3 files changed

Lines changed: 42 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Bump plumcp to 0.2.0-beta5.
66
- Fix auto-continue clobbering new prompt status and losing the stop button.
7+
- Add configurable shell for `shell_command` tool via `toolCall.shellCommand.path` and `toolCall.shellCommand.args`. #370
78

89
## 0.116.5
910

docs/config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,19 @@
840840
"markdownDescription": "Configuration for the shell_command tool.",
841841
"additionalProperties": false,
842842
"properties": {
843+
"path": {
844+
"type": "string",
845+
"description": "Custom shell executable path. Overrides the default platform shell (bash on Linux/macOS, powershell on Windows).",
846+
"markdownDescription": "Custom shell executable path. Overrides the default platform shell (`bash` on Linux/macOS, `powershell` on Windows)."
847+
},
848+
"args": {
849+
"type": "array",
850+
"items": {
851+
"type": "string"
852+
},
853+
"description": "Arguments passed to the shell before the script argument. E.g. [\"-l\", \"-c\"] for bash.",
854+
"markdownDescription": "Arguments passed to the shell before the script argument. E.g. `[\"-l\", \"-c\"]` for bash."
855+
},
843856
"summaryMaxLength": {
844857
"type": "integer",
845858
"description": "Maximum length of shell command summary.",

src/eca/features/tools/shell.clj

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,47 @@
1919
"Start a shell process, returning the process object for deref/management.
2020
2121
Options:
22-
- :cwd Working directory (required)
23-
- :script Inline script string (mutually exclusive with :file)
24-
- :file Script file path (mutually exclusive with :script)
25-
- :input String to pass as stdin (optional)
22+
- :cwd Working directory (required)
23+
- :script Inline script string (mutually exclusive with :file)
24+
- :file Script file path (mutually exclusive with :script)
25+
- :input String to pass as stdin (optional)
26+
- :shell-path Custom shell executable path (optional, overrides platform default)
27+
- :shell-args Custom shell args placed before the script/file arg (optional)
2628
2729
Returns: babashka.process process object (deref-able)"
28-
[{:keys [cwd script file input]}]
30+
[{:keys [cwd script file input shell-path shell-args]}]
2931
{:pre [(some? cwd)
3032
(or (some? script) (some? file))
3133
(not (and script file))]}
32-
(let [win? (string/starts-with? (System/getProperty "os.name") "Windows")
33-
cmd (cond
34-
(and win? file)
35-
["powershell.exe" "-ExecutionPolicy" "Bypass" "-File" file]
34+
(let [cmd (if shell-path
35+
(into (vec (cons shell-path shell-args)) [(or script file)])
36+
(let [win? (string/starts-with? (System/getProperty "os.name") "Windows")]
37+
(cond
38+
(and win? file)
39+
["powershell.exe" "-ExecutionPolicy" "Bypass" "-File" file]
3640

37-
(and win? script)
38-
["powershell.exe" "-NoProfile" "-Command" script]
41+
(and win? script)
42+
["powershell.exe" "-NoProfile" "-Command" script]
3943

40-
file
41-
["bash" file]
44+
file
45+
["bash" file]
4246

43-
:else
44-
["bash" "-c" script])]
47+
:else
48+
["bash" "-c" script])))]
4549
(p/process (cond-> {:cmd cmd
4650
:dir cwd
4751
:out :string
4852
:err :string
4953
:continue true}
5054
input (assoc :in input)))))
5155

52-
(defn ^:private shell-command [arguments {:keys [db tool-call-id call-state-fn state-transition-fn]}]
56+
(defn ^:private shell-command [arguments {:keys [db config tool-call-id call-state-fn state-transition-fn]}]
5357
(let [command-args (get arguments "command")
5458
user-work-dir (get arguments "working_directory")
55-
timeout (min (or (get arguments "timeout") default-timeout) max-timeout)]
59+
timeout (min (or (get arguments "timeout") default-timeout) max-timeout)
60+
shell-config (get-in config [:toolCall :shellCommand])
61+
shell-path (get shell-config :path)
62+
shell-args (get shell-config :args)]
5663
(or (tools.util/invalid-arguments arguments [["working_directory" #(or (nil? %)
5764
(fs/exists? %)) "working directory $working_directory does not exist"]])
5865
(let [work-dir (or (some-> user-work-dir fs/canonicalize str)
@@ -64,8 +71,10 @@
6471
_ (logger/debug logger-tag "Running command:" command-args)
6572
result (try
6673
(if-let [proc (when-not (= :stopping (:status (call-state-fn)))
67-
(start-shell-process! {:cwd work-dir
68-
:script command-args}))]
74+
(start-shell-process! (cond-> {:cwd work-dir
75+
:script command-args}
76+
shell-path (assoc :shell-path shell-path)
77+
shell-args (assoc :shell-args shell-args))))]
6978
(do
7079
(state-transition-fn :resources-created {:resources {:process proc}})
7180
(try (deref proc

0 commit comments

Comments
 (0)