1+ --- @class CodeCompanion.Tools.Orchestrator
2+ --- @field cancelled boolean ? Whether the tool execution has been cancelled
3+ --- @field id number The id of the tools coordinator
4+ --- @field index number The index of the current command
5+ --- @field handlers table<string , function>
6+ --- @field output table<string , function>
7+ --- @field queue CodeCompanion.Tools.Orchestrator.Queue
8+ --- @field status string The status of the tool execution " success" | " error"
9+ --- @field tool CodeCompanion.Tools.Tool The current tool being executed
10+ --- @field tool_output table ? The output collected from the tool
11+ --- @field tools CodeCompanion.Tools
12+ --- @field tool_handle vim.SystemObj ? SystemObj of the current tool execution
13+
114local Approvals = require (" codecompanion.interactions.chat.tools.approvals" )
215local Queue = require (" codecompanion.interactions.chat.tools.runtime.queue" )
316local Runner = require (" codecompanion.interactions.chat.tools.runtime.runner" )
@@ -7,6 +20,11 @@ local ui_utils = require("codecompanion.utils.ui")
720local utils = require (" codecompanion.utils" )
821
922local fmt = string.format
23+ local Orchestrator = {}
24+
25+ -- =============================================================================
26+ -- Private methods
27+ -- =============================================================================
1028
1129--- Strip any ANSI color codes which don't render in the chat buffer
1230--- @param tbl table
@@ -19,33 +37,32 @@ local function strip_ansi(tbl)
1937end
2038
2139--- Add a response to the chat buffer regarding a tool's execution
22- --- @param exec CodeCompanion.Tools.Orchestrator
2340--- @param llm_message string
2441--- @param user_message ? string
25- local send_response_to_chat = function ( exec , llm_message , user_message )
26- exec .tools .chat :add_tool_output (exec .tool , llm_message , user_message )
42+ function Orchestrator : _send_response_to_chat ( llm_message , user_message )
43+ self .tools .chat :add_tool_output (self .tool , llm_message , user_message )
2744end
2845
2946--- Execute a shell command with platform-specific handling
3047--- @param cmd table
3148--- @param callback function
32- local function execute_shell_command (cmd , callback )
49+ function Orchestrator : _execute_shell_command (cmd , callback )
3350 if vim .fn .has (" win32" ) == 1 then
3451 -- See PR #2186
3552 local shell_cmd = table.concat (cmd , " " ) .. " \r\n EXIT %ERRORLEVEL%\r\n "
36- vim .system ({ " cmd.exe" , " /Q" , " /K" }, {
53+ self . tool_handle = vim .system ({ " cmd.exe" , " /Q" , " /K" }, {
3754 stdin = shell_cmd ,
3855 env = { PROMPT = " \r\n " },
3956 }, callback )
4057 else
41- vim .system (os_utils .build_shell_command (cmd ), {}, callback )
58+ self . tool_handle = vim .system (os_utils .build_shell_command (cmd ), {}, callback )
4259 end
4360end
4461
4562--- Converts a cmd-based tool to a function-based tool.
4663--- @param tool CodeCompanion.Tools.Tool
4764--- @return CodeCompanion.Tools.Tool
48- local function cmd_to_func_tool (tool )
65+ function Orchestrator : _cmd_to_func_tool (tool )
4966 tool .cmds = vim
5067 .iter (tool .cmds )
5168 :map (function (cmd )
@@ -62,7 +79,7 @@ local function cmd_to_func_tool(tool)
6279 --- @param tools CodeCompanion.Tools
6380 return function (tools , _ , _ , cb )
6481 cb = vim .schedule_wrap (cb )
65- execute_shell_command (cmd , function (out )
82+ self : _execute_shell_command (cmd , function (out )
6683 if flag then
6784 tools .chat .tool_registry .flags = tools .chat .tool_registry .flags or {}
6885 tools .chat .tool_registry .flags [flag ] = (out .code == 0 )
@@ -93,17 +110,9 @@ local function cmd_to_func_tool(tool)
93110 return tool
94111end
95112
96- --- @class CodeCompanion.Tools.Orchestrator
97- --- @field id number The id of the tools coordinator
98- --- @field index number The index of the current command
99- --- @field handlers table<string , function>
100- --- @field output table<string , function>
101- --- @field queue CodeCompanion.Tools.Orchestrator.Queue
102- --- @field status string The status of the tool execution " success" | " error"
103- --- @field tool CodeCompanion.Tools.Tool The current tool being executed
104- --- @field tool_output table ? The output collected from the tool
105- --- @field tools CodeCompanion.Tools
106- local Orchestrator = {}
113+ -- =============================================================================
114+ -- Public methods
115+ -- =============================================================================
107116
108117--- @param tools CodeCompanion.Tools
109118--- @param id number
@@ -184,7 +193,7 @@ function Orchestrator:_setup_handlers()
184193 rejection = rejection .. fmt (' : "%s"' , opts .reason )
185194 end
186195 -- If no handler is set then return a default message
187- send_response_to_chat ( self , rejection )
196+ self : _send_response_to_chat ( rejection )
188197 end
189198 end ,
190199 error = function (cmd )
@@ -195,7 +204,7 @@ function Orchestrator:_setup_handlers()
195204 if self .tool .output and self .tool .output .error then
196205 self .tool .output .error (self .tool , self .tools , cmd , self .tools .stderr )
197206 else
198- send_response_to_chat ( self , fmt (" Error calling `%s`" , self .tool .name ))
207+ self : _send_response_to_chat ( fmt (" Error calling `%s`" , self .tool .name ))
199208 end
200209 end ,
201210 cancelled = function (cmd )
@@ -206,8 +215,7 @@ function Orchestrator:_setup_handlers()
206215 if self .tool .output and self .tool .output .cancelled then
207216 self .tool .output .cancelled (self .tool , self .tools , cmd )
208217 else
209- send_response_to_chat (
210- self ,
218+ self :_send_response_to_chat (
211219 fmt (" The user cancelled the execution of the %s tool" , self .tool .name ),
212220 fmt (" Cancelled `%s`" , self .tool .name )
213221 )
@@ -221,17 +229,18 @@ function Orchestrator:_setup_handlers()
221229 if self .tool .output and self .tool .output .success then
222230 self .tool .output .success (self .tool , self .tools , cmd , self .tool_output or {})
223231 else
224- send_response_to_chat ( self , fmt (" Executed `%s`" , self .tool .name ))
232+ self : _send_response_to_chat ( fmt (" Executed `%s`" , self .tool .name ))
225233 end
226234 end ,
227235 }
228236end
229237
230238--- When the tools coordinator is finished, finalize it via an autocmd
231- --- @param self CodeCompanion.Tools.Orchestrator
232239--- @return nil
233240function Orchestrator :_finalize_tools ()
234241 self .tools .tool = nil
242+ self .tools .chat .tool_orchestrator = nil
243+
235244 return utils .fire (" ToolsFinished" , { id = self .id , bufnr = self .tools .bufnr })
236245end
237246
@@ -251,7 +260,7 @@ function Orchestrator:setup_next_tool(input)
251260 self .handlers .setup () -- Call this early as cmd_runner needs to setup its cmds dynamically
252261
253262 -- Transform cmd-based tools to func-based
254- self .tool = cmd_to_func_tool (self .tool )
263+ self .tool = self : _cmd_to_func_tool (self .tool )
255264
256265 -- Get the first command to run
257266 local cmd = self .tool .cmds [1 ]
@@ -429,4 +438,43 @@ function Orchestrator:finalize_tool()
429438 end
430439end
431440
441+ --- Cancel the currently running tool execution
442+ --- @return nil
443+ function Orchestrator :cancel ()
444+ if self .cancelled then
445+ return
446+ end
447+
448+ log :debug (" Orchestrator:cancel" )
449+ self .cancelled = true
450+
451+ -- Kill the running system command if one exists
452+ if self .tool_handle then
453+ if vim .fn .has (" win32" ) == 1 then
454+ -- /F flag forces process to end
455+ -- /T ends child process (required since we are wrapping the child
456+ -- process in a parent cmd.exe process)
457+ vim .system ({ " taskkill" , " /F" , " /T" , " /PID" , tostring (self .tool_handle .pid ) })
458+ else
459+ self .tool_handle :kill (" sigkill" )
460+ end
461+
462+ self .tool_handle = nil
463+ end
464+
465+ -- Output the cancellation message to the chat buffer.
466+ if self .tool and self .output then
467+ self .output .cancelled (self .tool .cmds [1 ])
468+ end
469+
470+ -- Clean up the cancelled tool.
471+ self :finalize_tool ()
472+
473+ -- Cancel any pending tools in the queue.
474+ self :cancel_pending_tools ()
475+
476+ -- Close the current tool.
477+ self :_finalize_tools ()
478+ end
479+
432480return Orchestrator
0 commit comments