change-to-tools #2
@@ -50,12 +50,12 @@ function M.load()
|
|||||||
improved_debug = false,
|
improved_debug = false,
|
||||||
enable_chunking = false,
|
enable_chunking = false,
|
||||||
enable_step_by_step = true,
|
enable_step_by_step = true,
|
||||||
enable_debug_commands = false,
|
|
||||||
|
|
||||||
-- New table for tool auto-accept configuration
|
-- Removed enable_debug_commands
|
||||||
tool_auto_accept = {
|
tool_auto_accept = {
|
||||||
readFile = false,
|
readFile = false,
|
||||||
editFile = false,
|
editFile = false,
|
||||||
|
replace_in_file = false,
|
||||||
executeCommand = false,
|
executeCommand = false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,9 +106,6 @@ function M.load()
|
|||||||
if type(result.enable_step_by_step) == "boolean" then
|
if type(result.enable_step_by_step) == "boolean" then
|
||||||
config.enable_step_by_step = result.enable_step_by_step
|
config.enable_step_by_step = result.enable_step_by_step
|
||||||
end
|
end
|
||||||
if type(result.enable_debug_commands) == "boolean" then
|
|
||||||
config.enable_debug_commands = result.enable_debug_commands
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Load tool_auto_accept if present
|
-- Load tool_auto_accept if present
|
||||||
if type(result.tool_auto_accept) == "table" then
|
if type(result.tool_auto_accept) == "table" then
|
||||||
@@ -124,7 +121,7 @@ function M.load()
|
|||||||
config.initial_prompt = "You are a coding assistant who receives a project's context and user instructions..."
|
config.initial_prompt = "You are a coding assistant who receives a project's context and user instructions..."
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Merge the default prompt blocks with the config's initial prompt
|
-- Merge default prompt blocks
|
||||||
if type(config.default_prompt_blocks) == "table" and #config.default_prompt_blocks > 0 then
|
if type(config.default_prompt_blocks) == "table" and #config.default_prompt_blocks > 0 then
|
||||||
local merged_prompt = {}
|
local merged_prompt = {}
|
||||||
for _, block_name in ipairs(config.default_prompt_blocks) do
|
for _, block_name in ipairs(config.default_prompt_blocks) do
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ end
|
|||||||
-- PROMPT CONSTRUCTION
|
-- PROMPT CONSTRUCTION
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
local function build_tools_available_block()
|
local function build_tools_available_block()
|
||||||
-- We'll list each tool from tools_module.available_tools
|
|
||||||
local lines = {}
|
local lines = {}
|
||||||
lines[#lines+1] = "<tools_available>"
|
lines[#lines+1] = "<tools_available>"
|
||||||
for _, t in ipairs(tools_module.available_tools) do
|
for _, t in ipairs(tools_module.available_tools) do
|
||||||
@@ -94,7 +93,7 @@ local function build_prompt(user_input, dirs, conf)
|
|||||||
|
|
||||||
-- 3) <task>
|
-- 3) <task>
|
||||||
local task_lines = {}
|
local task_lines = {}
|
||||||
table.insert(task_lines, "<task>")
|
task_lines[#task_lines+1] = "<task>"
|
||||||
task_lines[#task_lines+1] = user_input
|
task_lines[#task_lines+1] = user_input
|
||||||
for _, file_path in ipairs(initial_files) do
|
for _, file_path in ipairs(initial_files) do
|
||||||
task_lines[#task_lines+1] = ("'%s' (see below for file content)"):format(file_path)
|
task_lines[#task_lines+1] = ("'%s' (see below for file content)"):format(file_path)
|
||||||
@@ -154,7 +153,7 @@ local function handle_step_by_step_if_needed(prompt, conf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
-- :ChatGPT Command
|
-- :ChatGPT
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
local function run_chatgpt_command()
|
local function run_chatgpt_command()
|
||||||
local conf = config.load()
|
local conf = config.load()
|
||||||
@@ -221,7 +220,7 @@ local function run_chatgpt_command()
|
|||||||
end
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
-- :ChatGPTPaste Command
|
-- :ChatGPTPaste
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
local function run_chatgpt_paste_command()
|
local function run_chatgpt_paste_command()
|
||||||
local conf = config.load()
|
local conf = config.load()
|
||||||
@@ -239,29 +238,21 @@ local function run_chatgpt_paste_command()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 1) Debug commands
|
-- Check if we have tools
|
||||||
if data.commands and conf.enable_debug_commands then
|
if data.tools then
|
||||||
local results = {}
|
-- Must also verify project name
|
||||||
for _, cmd in ipairs(data.commands) do
|
if not data.project_name or data.project_name ~= conf.project_name then
|
||||||
table.insert(results, require('chatgpt_nvim').execute_debug_command(cmd, conf))
|
vim.api.nvim_err_writeln("Project name mismatch or missing. Aborting tool usage.")
|
||||||
end
|
|
||||||
local output = table.concat(results, "\n\n")
|
|
||||||
copy_to_clipboard(output)
|
|
||||||
print("Debug command results copied to clipboard!")
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 2) Tools (handle multiple calls in one request)
|
|
||||||
if data.tools then
|
|
||||||
local output_messages = tools_manager.handle_tool_calls(data.tools, conf, is_subpath, read_file)
|
local output_messages = tools_manager.handle_tool_calls(data.tools, conf, is_subpath, read_file)
|
||||||
-- If the output is too large (over conf.prompt_char_limit?), we might respond with a special message.
|
|
||||||
copy_to_clipboard(output_messages)
|
copy_to_clipboard(output_messages)
|
||||||
print("Tool call results have been processed and copied to clipboard.")
|
print("Tool call results have been processed and copied to clipboard.")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 3) If we see project_name & files => final changes or file requests
|
-- If we see project_name & files => older YAML style. We handle it but it's discouraged now.
|
||||||
-- (If the user is still using older YAML style, we handle it, but not recommended.)
|
|
||||||
if data.project_name and data.files then
|
if data.project_name and data.files then
|
||||||
if data.project_name ~= conf.project_name then
|
if data.project_name ~= conf.project_name then
|
||||||
vim.api.nvim_err_writeln("Project name mismatch. Aborting.")
|
vim.api.nvim_err_writeln("Project name mismatch. Aborting.")
|
||||||
@@ -294,7 +285,6 @@ local function run_chatgpt_paste_command()
|
|||||||
vim.api.nvim_err_writeln("Preview not closed in time. Aborting.")
|
vim.api.nvim_err_writeln("Preview not closed in time. Aborting.")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local final_files = data.files
|
local final_files = data.files
|
||||||
@@ -354,7 +344,7 @@ local function run_chatgpt_paste_command()
|
|||||||
"\n\nProject name: " .. (conf.project_name or ""),
|
"\n\nProject name: " .. (conf.project_name or ""),
|
||||||
"\n\nBelow are the requested files from the project, each preceded by its filename in backticks and enclosed in triple backticks.\n",
|
"\n\nBelow are the requested files from the project, each preceded by its filename in backticks and enclosed in triple backticks.\n",
|
||||||
table.concat(file_sections, "\n"),
|
table.concat(file_sections, "\n"),
|
||||||
"\n\nIf you need more files, please respond again in YAML listing additional files. Or use the new 'tools' array approach to read/edit files. If you have all info, provide final changes or just proceed."
|
"\n\nIf you need more files, please respond again in YAML listing additional files, or use the 'tools:' approach. If you have all info, provide final changes or continue your instructions."
|
||||||
}
|
}
|
||||||
|
|
||||||
local prompt = table.concat(sections, "\n")
|
local prompt = table.concat(sections, "\n")
|
||||||
@@ -375,7 +365,7 @@ local function run_chatgpt_paste_command()
|
|||||||
print("Prompt (with requested files) copied to clipboard! Paste it into ChatGPT.")
|
print("Prompt (with requested files) copied to clipboard! Paste it into ChatGPT.")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
vim.api.nvim_err_writeln("Invalid response. Expected either 'tools' or 'project_name & files' in YAML.")
|
vim.api.nvim_err_writeln("No tools or recognized file instructions found. Provide 'tools:' or older 'project_name & files'.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -425,94 +415,4 @@ M.run_chatgpt_command = run_chatgpt_command
|
|||||||
M.run_chatgpt_paste_command = run_chatgpt_paste_command
|
M.run_chatgpt_paste_command = run_chatgpt_paste_command
|
||||||
M.run_chatgpt_current_buffer_command = run_chatgpt_current_buffer_command
|
M.run_chatgpt_current_buffer_command = run_chatgpt_current_buffer_command
|
||||||
|
|
||||||
-- Provide debug commands for "ls" and "grep"
|
|
||||||
M.execute_debug_command = function(cmd, conf)
|
|
||||||
if type(cmd) ~= "table" or not cmd.command then
|
|
||||||
return "Invalid command object."
|
|
||||||
end
|
|
||||||
local command = cmd.command
|
|
||||||
if command == "ls" then
|
|
||||||
local dir = cmd.dir or "."
|
|
||||||
local args = cmd.args or {}
|
|
||||||
local cmd_str = "ls"
|
|
||||||
if #args > 0 then
|
|
||||||
cmd_str = cmd_str .. " " .. table.concat(args, " ")
|
|
||||||
end
|
|
||||||
cmd_str = cmd_str .. " " .. dir
|
|
||||||
|
|
||||||
local handle = io.popen(cmd_str)
|
|
||||||
if not handle then
|
|
||||||
return "Failed to run ls command."
|
|
||||||
end
|
|
||||||
local result = handle:read("*a") or ""
|
|
||||||
handle:close()
|
|
||||||
return "Listing files in: " .. dir .. "\n" .. result
|
|
||||||
|
|
||||||
elseif command == "grep" then
|
|
||||||
local args = cmd.args or {}
|
|
||||||
if #args == 0 then
|
|
||||||
local pattern = cmd.pattern
|
|
||||||
local target = cmd.target
|
|
||||||
if not pattern or not target then
|
|
||||||
return "Usage for grep: {command='grep', args=['-r','pattern','target']} or {pattern='<text>', target='<path>'}"
|
|
||||||
end
|
|
||||||
local stat = vim.loop.fs_stat(target)
|
|
||||||
if not stat then
|
|
||||||
return "Cannot grep: target path does not exist"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function do_grep(search_string, filepath)
|
|
||||||
local c = read_file(filepath)
|
|
||||||
if not c then
|
|
||||||
return "Could not read file: " .. filepath
|
|
||||||
end
|
|
||||||
local lines = {}
|
|
||||||
local line_num = 0
|
|
||||||
for line in c:gmatch("([^\n]*)\n?") do
|
|
||||||
line_num = line_num + 1
|
|
||||||
if line:find(search_string, 1, true) then
|
|
||||||
lines[#lines+1] = filepath .. ":" .. line_num .. ":" .. line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return (#lines == 0) and ("No matches in " .. filepath) or table.concat(lines, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
if stat.type == "directory" then
|
|
||||||
local h = io.popen("ls -p " .. target .. " | grep -v /")
|
|
||||||
if not h then
|
|
||||||
return "Failed to read directory contents for grep."
|
|
||||||
end
|
|
||||||
local all_files = {}
|
|
||||||
for file in h:read("*a"):gmatch("[^\n]+") do
|
|
||||||
all_files[#all_files+1] = target .. "/" .. file
|
|
||||||
end
|
|
||||||
h:close()
|
|
||||||
local results = {}
|
|
||||||
for _, f in ipairs(all_files) do
|
|
||||||
local fstat = vim.loop.fs_stat(f)
|
|
||||||
if fstat and fstat.type == "file" then
|
|
||||||
results[#results+1] = do_grep(pattern, f)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.concat(results, "\n")
|
|
||||||
else
|
|
||||||
return do_grep(pattern, target)
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
-- new approach with flags/args
|
|
||||||
local cmd_str = "grep " .. table.concat(args, " ")
|
|
||||||
local handle = io.popen(cmd_str)
|
|
||||||
if not handle then
|
|
||||||
return "Failed to run grep command."
|
|
||||||
end
|
|
||||||
local result = handle:read("*a") or ""
|
|
||||||
handle:close()
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return "Unknown command: " .. command
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -256,54 +256,40 @@ local M = {
|
|||||||
["basic-prompt"] = [[
|
["basic-prompt"] = [[
|
||||||
### Basic Prompt
|
### Basic Prompt
|
||||||
|
|
||||||
1. **Analyze Required Files**
|
You are assisting me in a coding workflow for a project (e.g., "my_project"). Whenever you need to inspect or modify files, or execute commands, you must provide both:
|
||||||
- Check the project structure (or any provided file list).
|
|
||||||
- If you identify a file reference or function you do not have, **produce a YAML snippet** requesting that file’s content in full. For example:
|
1. `project_name: "<actual_project_name>"` (matching the real project name)
|
||||||
|
2. A `tools:` array describing the operations you want to perform.
|
||||||
|
|
||||||
|
**Example** (substitute `<actual_project_name>` with the real name):
|
||||||
```yaml
|
```yaml
|
||||||
project_name: my_project
|
project_name: "my_project"
|
||||||
files:
|
tools:
|
||||||
- path: "relative/path/to/needed_file"
|
- tool: "readFile"
|
||||||
```
|
path: "relative/path/to/file"
|
||||||
- Do not proceed with final changes if you lack the necessary file contents.
|
|
||||||
- Always provide the entire file content when you do include a file.
|
|
||||||
|
|
||||||
2. **Request File Contents**
|
- tool: "replace_in_file"
|
||||||
- For every file you need but don’t have, provide a short YAML snippet (like above) to retrieve it.
|
path: "relative/path/to/file"
|
||||||
- Ask any clarifying questions outside the YAML code block.
|
replacements:
|
||||||
|
- search: "old text"
|
||||||
|
replace: "new text"
|
||||||
|
|
||||||
3. **Provide Output YAML**
|
- tool: "editFile"
|
||||||
- When you have all information, output the final YAML in this format:
|
path: "relative/path/to/file"
|
||||||
```yaml
|
|
||||||
project_name: my_project
|
|
||||||
|
|
||||||
files:
|
|
||||||
- path: "src/main.py"
|
|
||||||
content: |
|
content: |
|
||||||
# Updated main function
|
# Full updated file content here
|
||||||
def main():
|
|
||||||
print("Hello, World!")
|
|
||||||
|
|
||||||
- path: "docs/README.md"
|
- tool: "executeCommand"
|
||||||
delete: true
|
command: "ls -la"
|
||||||
```
|
```
|
||||||
- `project_name` must match the project’s configured name.
|
|
||||||
- Each file item in `files` must have `path` and either `content` or `delete: true`.
|
|
||||||
|
|
||||||
4. **Explain Changes**
|
**Key Points**:
|
||||||
- After the final YAML, add a brief explanation of the modifications (outside the YAML).
|
- Always include `project_name: "<actual_project_name>"` in the same YAML as `tools`.
|
||||||
|
- If you only need one tool, include just one object in the `tools` array.
|
||||||
5. **Iterate as Needed**
|
- If multiple tools are needed, list them sequentially under `tools`.
|
||||||
- If further context or changes are required, repeat steps to request files or clarifications.
|
- Allways run at least one tool (e.g., `readFile`, `editFile`, `executeCommand`), exept you have finished.
|
||||||
|
- The plugin will verify the `project_name` is correct before running any tools.
|
||||||
6. **Important Notes**
|
- If the response grows too large, I'll guide you to break it into smaller steps.
|
||||||
- Never modify or delete a file you haven’t explicitly requested or received.
|
|
||||||
- Use comments in code only to clarify code logic, not to explain your thought process.
|
|
||||||
- Explanations go outside the YAML.
|
|
||||||
|
|
||||||
7. **Best Practices**
|
|
||||||
- Keep file paths accurate to avoid mistakes during implementation.
|
|
||||||
- Maintain a clear, logical structure for your changes.
|
|
||||||
- Use the final YAML consistently for clarity.
|
|
||||||
]],
|
]],
|
||||||
["secure-coding"] = [[
|
["secure-coding"] = [[
|
||||||
### Secure Coding Guidelines
|
### Secure Coding Guidelines
|
||||||
@@ -370,33 +356,10 @@ local M = {
|
|||||||
["step-prompt"] = [[
|
["step-prompt"] = [[
|
||||||
It appears this request might exceed the model's prompt character limit if done all at once.
|
It appears this request might exceed the model's prompt character limit if done all at once.
|
||||||
Please break down the tasks into smaller steps and handle them one by one.
|
Please break down the tasks into smaller steps and handle them one by one.
|
||||||
At each step, we'll provide relevant files or context if needed.
|
|
||||||
Thank you!
|
Thank you!
|
||||||
]],
|
]],
|
||||||
["file-selection-instructions"] = [[
|
["file-selection-instructions"] = [[
|
||||||
Delete lines for directories you do NOT want, then save & close (e.g. :wq, :x, or :bd)
|
Delete lines for directories you do NOT want, then save & close (e.g. :wq, :x, or :bd)
|
||||||
]],
|
|
||||||
["debug-commands-info"] = [[
|
|
||||||
### Debugging Commands
|
|
||||||
|
|
||||||
If you need debugging commands, include them in your YAML response as follows:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
commands:
|
|
||||||
- command: "ls"
|
|
||||||
args: ["-l", "path/to/directory"]
|
|
||||||
|
|
||||||
- command: "grep"
|
|
||||||
args: ["-r", "searchString", "path/to/file/or/directory"]
|
|
||||||
```
|
|
||||||
|
|
||||||
The "ls" command uses the system's 'ls' command to list directory contents. You can pass flags or additional arguments in `args`.
|
|
||||||
The "grep" command searches for a given pattern in files or directories, again receiving flags or additional arguments in `args`.
|
|
||||||
If you omit `args` for grep, you can still use the older format with `pattern` and `target` for backward compatibility.
|
|
||||||
|
|
||||||
This are the only two commands supported at the moment.
|
|
||||||
|
|
||||||
When these commands are present and `enable_debug_commands` is true, I'll execute them and return the results in the clipboard.
|
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
local tools_module = require("chatgpt_nvim.tools")
|
local tools_module = require("chatgpt_nvim.tools")
|
||||||
local uv = vim.loop
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
-- Simple destructive command check
|
|
||||||
local function is_destructive_command(cmd)
|
local function is_destructive_command(cmd)
|
||||||
if not cmd then return false end
|
if not cmd then return false end
|
||||||
local destructive_list = { "rm", "sudo", "mv", "cp" }
|
local destructive_list = { "rm", "sudo", "mv", "cp" }
|
||||||
@@ -14,7 +13,6 @@ local function is_destructive_command(cmd)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Prompt user if not auto-accepted or if command is destructive
|
|
||||||
local function prompt_user_tool_accept(tool_call, conf)
|
local function prompt_user_tool_accept(tool_call, conf)
|
||||||
local function ask_user(msg)
|
local function ask_user(msg)
|
||||||
vim.api.nvim_out_write(msg .. " [y/N]: ")
|
vim.api.nvim_out_write(msg .. " [y/N]: ")
|
||||||
@@ -39,11 +37,9 @@ local function prompt_user_tool_accept(tool_call, conf)
|
|||||||
end
|
end
|
||||||
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 function handle_tool_calls(tools, conf, is_subpath_fn, read_file_fn)
|
||||||
local messages = {}
|
local messages = {}
|
||||||
for _, call in ipairs(tools) do
|
for _, call in ipairs(tools) do
|
||||||
-- Prompt user acceptance
|
|
||||||
local accepted = prompt_user_tool_accept(call, conf)
|
local accepted = prompt_user_tool_accept(call, conf)
|
||||||
if not accepted then
|
if not accepted then
|
||||||
table.insert(messages, string.format("Tool [%s] was rejected by user.", call.tool or "nil"))
|
table.insert(messages, string.format("Tool [%s] was rejected by user.", call.tool or "nil"))
|
||||||
@@ -58,7 +54,12 @@ local function handle_tool_calls(tools, conf, is_subpath_fn, read_file_fn)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(messages, "\n\n")
|
local combined = table.concat(messages, "\n\n")
|
||||||
|
local limit = conf.prompt_char_limit or 8000
|
||||||
|
if #combined > limit then
|
||||||
|
return "The combined tool output is too large ("..#combined.."). Please break down the operations into smaller steps."
|
||||||
|
end
|
||||||
|
return combined
|
||||||
end
|
end
|
||||||
|
|
||||||
M.handle_tool_calls = handle_tool_calls
|
M.handle_tool_calls = handle_tool_calls
|
||||||
|
|||||||
Reference in New Issue
Block a user