change-to-tools #2

Merged
dominik.polakovics merged 5 commits from change-to-tools into main 2025-01-31 13:38:30 +01:00
4 changed files with 63 additions and 202 deletions
Showing only changes of commit 0ff77954db - Show all commits

View File

@@ -50,12 +50,12 @@ function M.load()
improved_debug = false,
enable_chunking = false,
enable_step_by_step = true,
enable_debug_commands = false,
-- New table for tool auto-accept configuration
-- Removed enable_debug_commands
tool_auto_accept = {
readFile = false,
editFile = false,
replace_in_file = false,
executeCommand = false,
}
}
@@ -106,9 +106,6 @@ function M.load()
if type(result.enable_step_by_step) == "boolean" then
config.enable_step_by_step = result.enable_step_by_step
end
if type(result.enable_debug_commands) == "boolean" then
config.enable_debug_commands = result.enable_debug_commands
end
-- Load tool_auto_accept if present
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..."
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
local merged_prompt = {}
for _, block_name in ipairs(config.default_prompt_blocks) do

View File

@@ -67,7 +67,6 @@ end
-- PROMPT CONSTRUCTION
------------------------------------------------------------------------------
local function build_tools_available_block()
-- We'll list each tool from tools_module.available_tools
local lines = {}
lines[#lines+1] = "<tools_available>"
for _, t in ipairs(tools_module.available_tools) do
@@ -94,7 +93,7 @@ local function build_prompt(user_input, dirs, conf)
-- 3) <task>
local task_lines = {}
table.insert(task_lines, "<task>")
task_lines[#task_lines+1] = "<task>"
task_lines[#task_lines+1] = user_input
for _, file_path in ipairs(initial_files) do
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
------------------------------------------------------------------------------
-- :ChatGPT Command
-- :ChatGPT
------------------------------------------------------------------------------
local function run_chatgpt_command()
local conf = config.load()
@@ -221,7 +220,7 @@ local function run_chatgpt_command()
end
------------------------------------------------------------------------------
-- :ChatGPTPaste Command
-- :ChatGPTPaste
------------------------------------------------------------------------------
local function run_chatgpt_paste_command()
local conf = config.load()
@@ -239,29 +238,21 @@ local function run_chatgpt_paste_command()
return
end
-- 1) Debug commands
if data.commands and conf.enable_debug_commands then
local results = {}
for _, cmd in ipairs(data.commands) do
table.insert(results, require('chatgpt_nvim').execute_debug_command(cmd, conf))
end
local output = table.concat(results, "\n\n")
copy_to_clipboard(output)
print("Debug command results copied to clipboard!")
-- Check if we have tools
if data.tools then
-- Must also verify project name
if not data.project_name or data.project_name ~= conf.project_name then
vim.api.nvim_err_writeln("Project name mismatch or missing. Aborting tool usage.")
return
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)
-- If the output is too large (over conf.prompt_char_limit?), we might respond with a special message.
copy_to_clipboard(output_messages)
print("Tool call results have been processed and copied to clipboard.")
return
end
-- 3) If we see project_name & files => final changes or file requests
-- (If the user is still using older YAML style, we handle it, but not recommended.)
-- If we see project_name & files => older YAML style. We handle it but it's discouraged now.
if data.project_name and data.files then
if data.project_name ~= conf.project_name then
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.")
return
end
end
local final_files = data.files
@@ -354,7 +344,7 @@ local function run_chatgpt_paste_command()
"\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",
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")
@@ -375,7 +365,7 @@ local function run_chatgpt_paste_command()
print("Prompt (with requested files) copied to clipboard! Paste it into ChatGPT.")
end
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
@@ -425,94 +415,4 @@ M.run_chatgpt_command = run_chatgpt_command
M.run_chatgpt_paste_command = run_chatgpt_paste_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

View File

@@ -256,54 +256,40 @@ local M = {
["basic-prompt"] = [[
### Basic Prompt
1. **Analyze Required Files**
- 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 files content in full. For example:
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:
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
project_name: my_project
files:
- path: "relative/path/to/needed_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.
project_name: "my_project"
tools:
- tool: "readFile"
path: "relative/path/to/file"
2. **Request File Contents**
- For every file you need but dont have, provide a short YAML snippet (like above) to retrieve it.
- Ask any clarifying questions outside the YAML code block.
- tool: "replace_in_file"
path: "relative/path/to/file"
replacements:
- search: "old text"
replace: "new text"
3. **Provide Output YAML**
- When you have all information, output the final YAML in this format:
```yaml
project_name: my_project
files:
- path: "src/main.py"
- tool: "editFile"
path: "relative/path/to/file"
content: |
# Updated main function
def main():
print("Hello, World!")
# Full updated file content here
- path: "docs/README.md"
delete: true
- tool: "executeCommand"
command: "ls -la"
```
- `project_name` must match the projects configured name.
- Each file item in `files` must have `path` and either `content` or `delete: true`.
4. **Explain Changes**
- After the final YAML, add a brief explanation of the modifications (outside the YAML).
5. **Iterate as Needed**
- If further context or changes are required, repeat steps to request files or clarifications.
6. **Important Notes**
- Never modify or delete a file you havent 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.
**Key Points**:
- 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.
- If multiple tools are needed, list them sequentially under `tools`.
- 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.
- If the response grows too large, I'll guide you to break it into smaller steps.
]],
["secure-coding"] = [[
### Secure Coding Guidelines
@@ -370,33 +356,10 @@ local M = {
["step-prompt"] = [[
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.
At each step, we'll provide relevant files or context if needed.
Thank you!
]],
["file-selection-instructions"] = [[
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.
]]
}

View File

@@ -1,8 +1,7 @@
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" }
@@ -14,7 +13,6 @@ local function is_destructive_command(cmd)
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]: ")
@@ -39,11 +37,9 @@ local function prompt_user_tool_accept(tool_call, conf)
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"))
@@ -58,7 +54,12 @@ local function handle_tool_calls(tools, conf, is_subpath_fn, read_file_fn)
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
M.handle_tool_calls = handle_tool_calls