initial change to tools logic
This commit is contained in:
27
lua/chatgpt_nvim/tools/edit_file.lua
Normal file
27
lua/chatgpt_nvim/tools/edit_file.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
local handler = require("chatgpt_nvim.handler")
|
||||
local M = {}
|
||||
|
||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||
local path = tool_call.path
|
||||
local new_content = tool_call.content
|
||||
|
||||
if not path or not new_content then
|
||||
return "[edit_file] Missing 'path' or 'content'."
|
||||
end
|
||||
local root = vim.fn.getcwd()
|
||||
|
||||
if not is_subpath(root, path) then
|
||||
return string.format("Tool [edit_file for '%s'] REJECTED. Path outside project root.", path)
|
||||
end
|
||||
|
||||
handler.write_file(path, new_content, conf)
|
||||
local msg = {}
|
||||
msg[#msg+1] = string.format("Tool [edit_file for '%s'] Result:\nThe content was successfully saved to %s.", path, path)
|
||||
msg[#msg+1] = "\nHere is the full, updated content of the file that was saved:\n"
|
||||
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, new_content)
|
||||
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
||||
|
||||
return table.concat(msg, "")
|
||||
end
|
||||
|
||||
return M
|
||||
17
lua/chatgpt_nvim/tools/execute_command.lua
Normal file
17
lua/chatgpt_nvim/tools/execute_command.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
local M = {}
|
||||
|
||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||
local cmd = tool_call.command
|
||||
if not cmd then
|
||||
return "[executeCommand] Missing 'command'."
|
||||
end
|
||||
local handle = io.popen(cmd)
|
||||
if not handle then
|
||||
return string.format("Tool [executeCommand '%s'] FAILED to popen.", cmd)
|
||||
end
|
||||
local result = handle:read("*a") or ""
|
||||
handle:close()
|
||||
return string.format("Tool [executeCommand '%s'] Result:\n%s", cmd, result)
|
||||
end
|
||||
|
||||
return M
|
||||
39
lua/chatgpt_nvim/tools/init.lua
Normal file
39
lua/chatgpt_nvim/tools/init.lua
Normal file
@@ -0,0 +1,39 @@
|
||||
local read_file_tool = require("chatgpt_nvim.tools.read_file")
|
||||
local edit_file_tool = require("chatgpt_nvim.tools.edit_file")
|
||||
local replace_in_file_tool = require("chatgpt_nvim.tools.replace_in_file")
|
||||
local execute_command_tool = require("chatgpt_nvim.tools.execute_command")
|
||||
|
||||
local M = {}
|
||||
|
||||
-- We can store a table of available tools here
|
||||
M.available_tools = {
|
||||
{
|
||||
name = "readFile",
|
||||
usage = "Retrieve the contents of a file. Provide { tool='readFile', path='...' }",
|
||||
explanation = "Use this to read file content directly from the disk."
|
||||
},
|
||||
{
|
||||
name = "editFile",
|
||||
usage = "Overwrite an entire file's content. Provide { tool='editFile', path='...', content='...' }",
|
||||
explanation = "Use this when you want to replace a file with new content."
|
||||
},
|
||||
{
|
||||
name = "replace_in_file",
|
||||
usage = "Perform a search-and-replace. Provide { tool='replace_in_file', path='...', replacements=[ { search='...', replace='...' }, ... ] }",
|
||||
explanation = "Use this to apply incremental changes without fully overwriting the file."
|
||||
},
|
||||
{
|
||||
name = "executeCommand",
|
||||
usage = "Run a shell command. Provide { tool='executeCommand', command='...' }",
|
||||
explanation = "Use with caution, especially for destructive operations (rm, sudo, etc.)."
|
||||
},
|
||||
}
|
||||
|
||||
M.tools_by_name = {
|
||||
readFile = read_file_tool,
|
||||
editFile = edit_file_tool,
|
||||
replace_in_file = replace_in_file_tool,
|
||||
executeCommand = execute_command_tool
|
||||
}
|
||||
|
||||
return M
|
||||
65
lua/chatgpt_nvim/tools/manager.lua
Normal file
65
lua/chatgpt_nvim/tools/manager.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
local tools_module = require("chatgpt_nvim.tools")
|
||||
local uv = vim.loop
|
||||
local M = {}
|
||||
|
||||
-- Simple destructive command check
|
||||
local function is_destructive_command(cmd)
|
||||
if not cmd then return false end
|
||||
local destructive_list = { "rm", "sudo", "mv", "cp" }
|
||||
for _, keyword in ipairs(destructive_list) do
|
||||
if cmd:match("(^" .. keyword .. "[%s$])") or cmd:match("[%s]" .. keyword .. "[%s$]") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Prompt user if not auto-accepted or if command is destructive
|
||||
local function prompt_user_tool_accept(tool_call, conf)
|
||||
local function ask_user(msg)
|
||||
vim.api.nvim_out_write(msg .. " [y/N]: ")
|
||||
local ans = vim.fn.input("")
|
||||
if ans:lower() == "y" then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local auto_accept = conf.tool_auto_accept[tool_call.tool]
|
||||
if tool_call.tool == "executeCommand" and auto_accept then
|
||||
if is_destructive_command(tool_call.command) then
|
||||
auto_accept = false
|
||||
end
|
||||
end
|
||||
|
||||
if not auto_accept then
|
||||
return ask_user(("Tool request: %s -> Accept?"):format(tool_call.tool or "unknown"))
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- We'll pass references to `read_file` from init.
|
||||
local function handle_tool_calls(tools, conf, is_subpath_fn, read_file_fn)
|
||||
local messages = {}
|
||||
for _, call in ipairs(tools) do
|
||||
-- Prompt user acceptance
|
||||
local accepted = prompt_user_tool_accept(call, conf)
|
||||
if not accepted then
|
||||
table.insert(messages, string.format("Tool [%s] was rejected by user.", call.tool or "nil"))
|
||||
else
|
||||
local tool_impl = tools_module.tools_by_name[call.tool]
|
||||
if tool_impl then
|
||||
local msg = tool_impl.run(call, conf, prompt_user_tool_accept, is_subpath_fn, read_file_fn)
|
||||
table.insert(messages, msg)
|
||||
else
|
||||
table.insert(messages, string.format("Unknown tool type: '%s'", call.tool or "nil"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(messages, "\n\n")
|
||||
end
|
||||
|
||||
M.handle_tool_calls = handle_tool_calls
|
||||
return M
|
||||
17
lua/chatgpt_nvim/tools/read_file.lua
Normal file
17
lua/chatgpt_nvim/tools/read_file.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
local uv = vim.loop
|
||||
local M = {}
|
||||
|
||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||
local path = tool_call.path
|
||||
if not path then
|
||||
return "[read_file] Missing 'path'."
|
||||
end
|
||||
local file_data = read_file(path)
|
||||
if file_data then
|
||||
return string.format("Tool [read_file for '%s'] Result:\n\n%s", path, file_data)
|
||||
else
|
||||
return string.format("Tool [read_file for '%s'] FAILED. File not found or not readable.", path)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
47
lua/chatgpt_nvim/tools/replace_in_file.lua
Normal file
47
lua/chatgpt_nvim/tools/replace_in_file.lua
Normal file
@@ -0,0 +1,47 @@
|
||||
local handler = require("chatgpt_nvim.handler")
|
||||
|
||||
local M = {}
|
||||
|
||||
local function search_and_replace(original, replacements)
|
||||
-- Basic approach: do a global plain text replace for each entry
|
||||
local updated = original
|
||||
for _, r in ipairs(replacements) do
|
||||
local search_str = r.search or ""
|
||||
local replace_str = r.replace or ""
|
||||
-- Here we do a global plain text replacement
|
||||
updated = updated:gsub(search_str, replace_str)
|
||||
end
|
||||
return updated
|
||||
end
|
||||
|
||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||
local path = tool_call.path
|
||||
local replacements = tool_call.replacements or {}
|
||||
|
||||
if not path or #replacements == 0 then
|
||||
return "[replace_in_file] Missing 'path' or 'replacements'."
|
||||
end
|
||||
local root = vim.fn.getcwd()
|
||||
|
||||
if not is_subpath(root, path) then
|
||||
return string.format("Tool [replace_in_file for '%s'] REJECTED. Path outside project root.", path)
|
||||
end
|
||||
|
||||
local orig_data = read_file(path)
|
||||
if not orig_data then
|
||||
return string.format("[replace_in_file for '%s'] FAILED. Could not read file.", path)
|
||||
end
|
||||
|
||||
local updated_data = search_and_replace(orig_data, replacements)
|
||||
handler.write_file(path, updated_data, conf)
|
||||
|
||||
local msg = {}
|
||||
msg[#msg+1] = string.format("[replace_in_file for '%s'] Result:\nThe content was successfully saved to %s.", path, path)
|
||||
msg[#msg+1] = "\nHere is the full, updated content of the file that was saved:\n"
|
||||
msg[#msg+1] = string.format("<final_file_content path=\"%s\">\n%s\n</final_file_content>", path, updated_data)
|
||||
msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n"
|
||||
|
||||
return table.concat(msg, "")
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user