diff --git a/lua/avante/llm.lua b/lua/avante/llm.lua index a31ed5b..ce96a7d 100644 --- a/lua/avante/llm.lua +++ b/lua/avante/llm.lua @@ -182,7 +182,8 @@ function M.generate_prompts(opts) local latest_tool_id = viewed_files[path] if not latest_tool_id then goto continue end if latest_tool_id ~= item.tool_use_id then - item.content = string.format("The file %s has been updated. Please use the latest view tool result!", path) + item.content = + string.format("The file %s has been updated. Please use the latest `view` tool result!", path) else local lines, error = Utils.read_file_from_buf_or_disk(path) if error ~= nil then Utils.error("error reading file: " .. error) end @@ -288,18 +289,20 @@ function M.generate_prompts(opts) dropped_history_messages = vim.list_extend(dropped_history_messages, opts.prompt_opts.dropped_history_messages) end + local cleaned_history_messages = opts.history_messages + local final_history_messages = {} - if opts.history_messages then + if cleaned_history_messages then if opts.disable_compact_history_messages then - final_history_messages = vim.list_extend(final_history_messages, opts.history_messages) + final_history_messages = vim.list_extend(final_history_messages, cleaned_history_messages) else if Config.history.max_tokens > 0 then remaining_tokens = math.min(Config.history.max_tokens, remaining_tokens) end -- Traverse the history in reverse, keeping only the latest history until the remaining tokens are exhausted and the first message role is "user" local history_messages = {} - for i = #opts.history_messages, 1, -1 do - local message = opts.history_messages[i] + for i = #cleaned_history_messages, 1, -1 do + local message = cleaned_history_messages[i] local tokens = Utils.tokens.calculate_tokens(message.message.content) remaining_tokens = remaining_tokens - tokens if remaining_tokens > 0 then @@ -310,10 +313,12 @@ function M.generate_prompts(opts) end if #history_messages == 0 then - history_messages = vim.list_slice(opts.history_messages, #opts.history_messages - 1, #opts.history_messages) + history_messages = + vim.list_slice(cleaned_history_messages, #cleaned_history_messages - 1, #cleaned_history_messages) end - dropped_history_messages = vim.list_slice(opts.history_messages, 1, #opts.history_messages - #history_messages) + dropped_history_messages = + vim.list_slice(cleaned_history_messages, 1, #cleaned_history_messages - #history_messages) -- prepend the history messages to the messages table vim.iter(history_messages):each(function(msg) table.insert(final_history_messages, msg) end) diff --git a/lua/avante/llm_tools/replace_in_file.lua b/lua/avante/llm_tools/replace_in_file.lua index 2fd6534..d77e603 100644 --- a/lua/avante/llm_tools/replace_in_file.lua +++ b/lua/avante/llm_tools/replace_in_file.lua @@ -38,6 +38,7 @@ One or more SEARCH/REPLACE blocks following this exact format: \`\`\` Critical rules: 1. SEARCH content must match the associated file section to find EXACTLY: + * Do not refer to the `diff` argument of the previous `replace_in_file` function call for SEARCH content matching, as it may have been modified. Always match from the latest file content in or from the `view` function call result. * Match character-for-character including whitespace, indentation, line endings * Include all comments, docstrings, etc. 2. SEARCH/REPLACE blocks will ONLY replace the first match occurrence. diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index 0fd705a..597f32a 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -2311,22 +2311,23 @@ function Sidebar:create_input_container() ---@param state AvanteLLMToolUseState local function on_tool_log(tool_id, tool_name, log, state) if state == "generating" then on_state_change("tool calling") end - local tool_message = vim.iter(self.chat_history.messages):find(function(message) - if message.message.role ~= "assistant" then return false end + local tool_use_message = nil + for idx = #self.chat_history.messages, 1, -1 do + local message = self.chat_history.messages[idx] local content = message.message.content - if type(content) ~= "table" then return false end - if content[1].type ~= "tool_use" then return false end - if content[1].id ~= tool_id then return false end - return true - end) - if not tool_message then - Utils.debug("tool_message not found", tool_id, tool_name) + if type(content) == "table" and content[1].type == "tool_use" and content[1].id == tool_id then + tool_use_message = message + break + end + end + if not tool_use_message then + Utils.debug("tool_use message not found", tool_id, tool_name) return end - local tool_use_logs = tool_message.tool_use_logs or {} + local tool_use_logs = tool_use_message.tool_use_logs or {} local content = string.format("[%s]: %s", tool_name, log) table.insert(tool_use_logs, content) - tool_message.tool_use_logs = tool_use_logs + tool_use_message.tool_use_logs = tool_use_logs save_history() self:update_content("") end diff --git a/lua/avante/utils/init.lua b/lua/avante/utils/init.lua index b267458..c3c761b 100644 --- a/lua/avante/utils/init.lua +++ b/lua/avante/utils/init.lua @@ -1413,6 +1413,66 @@ function M.uuid() end) end +---@param message avante.HistoryMessage +---@return boolean +function M.is_tool_use_message(message) + local content = message.message.content + if type(content) == "string" then return false end + if vim.islist(content) then + for _, item in ipairs(content) do + if item.type == "tool_use" then return true end + end + end + return false +end + +---@param message avante.HistoryMessage +---@return boolean +function M.is_tool_result_message(message) + local content = message.message.content + if type(content) == "string" then return false end + if vim.islist(content) then + for _, item in ipairs(content) do + if item.type == "tool_result" then return true end + end + end + return false +end + +---@param message avante.HistoryMessage +---@param messages avante.HistoryMessage[] +---@return avante.HistoryMessage | nil +function M.get_tool_use_message(message, messages) + local content = message.message.content + if type(content) == "string" then return nil end + if vim.islist(content) then + local tool_id = nil + for _, item in ipairs(content) do + if item.type == "tool_result" then + tool_id = item.tool_use_id + break + end + end + if not tool_id then return nil end + local idx = nil + for idx_, message_ in ipairs(messages) do + if message_.uuid == message.uuid then + idx = idx_ + break + end + end + if not idx then return nil end + for idx_ = idx - 1, 1, -1 do + local message_ = messages[idx_] + local content_ = message_.message.content + if type(content_) == "table" and content_[1].type == "tool_use" and content_[1].id == tool_id then + return message_ + end + end + end + return nil +end + ---@param message avante.HistoryMessage ---@param messages avante.HistoryMessage[] ---@return avante.HistoryMessage | nil @@ -1428,7 +1488,15 @@ function M.get_tool_result_message(message, messages) end end if not tool_id then return nil end - for _, message_ in ipairs(messages) do + local idx = nil + for idx_, message_ in ipairs(messages) do + if message_.uuid == message.uuid then + idx = idx_ + break + end + end + if not idx then return nil end + for _, message_ in ipairs(vim.list_slice(messages, idx + 1, #messages)) do local content_ = message_.message.content if type(content_) == "table" and content_[1].type == "tool_result" and content_[1].tool_use_id == tool_id then return message_ @@ -1469,36 +1537,48 @@ function M.message_content_item_to_lines(item, message, messages) local lines = {} local state = "generating" local hl = "AvanteStateSpinnerToolCalling" - if message.state == "generated" then - local tool_result_message = M.get_tool_result_message(message, messages) - if tool_result_message then - local tool_result = tool_result_message.message.content[1] - if tool_result.is_error then - state = "failed" - hl = "AvanteStateSpinnerFailed" - else - state = "succeeded" - hl = "AvanteStateSpinnerSucceeded" - end + local tool_result_message = M.get_tool_result_message(message, messages) + if tool_result_message then + local tool_result = tool_result_message.message.content[1] + if tool_result.is_error then + state = "failed" + hl = "AvanteStateSpinnerFailed" + else + state = "succeeded" + hl = "AvanteStateSpinnerSucceeded" end end table.insert( lines, Line:new({ { "╭─" }, { " " }, { string.format(" %s ", item.name), hl }, { string.format(" %s", state) } }) ) - for idx, log in ipairs(message.tool_use_logs or {}) do - local log_ = M.trim(log, { prefix = string.format("[%s]: ", item.name) }) - local lines_ = vim.split(log_, "\n") - if idx ~= #(message.tool_use_logs or {}) then - for _, line_ in ipairs(lines_) do - table.insert(lines, Line:new({ { "│" }, { string.format(" %s", line_) } })) - end - else - for idx_, line_ in ipairs(lines_) do - if idx_ ~= #lines_ then + if message.tool_use_logs then + for idx, log in ipairs(message.tool_use_logs) do + local log_ = M.trim(log, { prefix = string.format("[%s]: ", item.name) }) + local lines_ = vim.split(log_, "\n") + if idx ~= #(message.tool_use_logs or {}) then + for _, line_ in ipairs(lines_) do table.insert(lines, Line:new({ { "│" }, { string.format(" %s", line_) } })) + end + else + for idx_, line_ in ipairs(lines_) do + if idx_ ~= #lines_ then + table.insert(lines, Line:new({ { "│" }, { string.format(" %s", line_) } })) + else + table.insert(lines, Line:new({ { "╰─" }, { string.format(" %s", line_) } })) + end + end + end + end + elseif tool_result_message then + local tool_result = tool_result_message.message.content[1] + if tool_result.content then + local result_lines = vim.split(tool_result.content, "\n") + for idx, line in ipairs(result_lines) do + if idx ~= #result_lines then + table.insert(lines, Line:new({ { "│" }, { string.format(" %s", line) } })) else - table.insert(lines, Line:new({ { "╰─" }, { string.format(" %s", line_) } })) + table.insert(lines, Line:new({ { "╰─" }, { string.format(" %s", line) } })) end end end