fixing the issues on the tags
This commit is contained in:
163
lua/codetyper/agent/context_modal.lua
Normal file
163
lua/codetyper/agent/context_modal.lua
Normal file
@@ -0,0 +1,163 @@
|
||||
---@mod codetyper.agent.context_modal Modal for additional context input
|
||||
---@brief [[
|
||||
--- Opens a floating window for user to provide additional context
|
||||
--- when the LLM requests more information.
|
||||
---@brief ]]
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class ContextModalState
|
||||
---@field buf number|nil Buffer number
|
||||
---@field win number|nil Window number
|
||||
---@field original_event table|nil Original prompt event
|
||||
---@field callback function|nil Callback with additional context
|
||||
---@field llm_response string|nil LLM's response asking for context
|
||||
|
||||
local state = {
|
||||
buf = nil,
|
||||
win = nil,
|
||||
original_event = nil,
|
||||
callback = nil,
|
||||
llm_response = nil,
|
||||
}
|
||||
|
||||
--- Close the context modal
|
||||
function M.close()
|
||||
if state.win and vim.api.nvim_win_is_valid(state.win) then
|
||||
vim.api.nvim_win_close(state.win, true)
|
||||
end
|
||||
if state.buf and vim.api.nvim_buf_is_valid(state.buf) then
|
||||
vim.api.nvim_buf_delete(state.buf, { force = true })
|
||||
end
|
||||
state.win = nil
|
||||
state.buf = nil
|
||||
state.original_event = nil
|
||||
state.callback = nil
|
||||
state.llm_response = nil
|
||||
end
|
||||
|
||||
--- Submit the additional context
|
||||
local function submit()
|
||||
if not state.buf or not vim.api.nvim_buf_is_valid(state.buf) then
|
||||
return
|
||||
end
|
||||
|
||||
local lines = vim.api.nvim_buf_get_lines(state.buf, 0, -1, false)
|
||||
local additional_context = table.concat(lines, "\n")
|
||||
|
||||
-- Trim whitespace
|
||||
additional_context = additional_context:match("^%s*(.-)%s*$") or additional_context
|
||||
|
||||
if additional_context == "" then
|
||||
M.close()
|
||||
return
|
||||
end
|
||||
|
||||
local original_event = state.original_event
|
||||
local callback = state.callback
|
||||
|
||||
M.close()
|
||||
|
||||
if callback and original_event then
|
||||
callback(original_event, additional_context)
|
||||
end
|
||||
end
|
||||
|
||||
--- Open the context modal
|
||||
---@param original_event table Original prompt event
|
||||
---@param llm_response string LLM's response asking for context
|
||||
---@param callback function(event: table, additional_context: string)
|
||||
function M.open(original_event, llm_response, callback)
|
||||
-- Close any existing modal
|
||||
M.close()
|
||||
|
||||
state.original_event = original_event
|
||||
state.llm_response = llm_response
|
||||
state.callback = callback
|
||||
|
||||
-- Calculate window size
|
||||
local width = math.min(80, vim.o.columns - 10)
|
||||
local height = 10
|
||||
|
||||
-- Create buffer
|
||||
state.buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[state.buf].buftype = "nofile"
|
||||
vim.bo[state.buf].bufhidden = "wipe"
|
||||
vim.bo[state.buf].filetype = "markdown"
|
||||
|
||||
-- Create window
|
||||
local row = math.floor((vim.o.lines - height) / 2)
|
||||
local col = math.floor((vim.o.columns - width) / 2)
|
||||
|
||||
state.win = vim.api.nvim_open_win(state.buf, true, {
|
||||
relative = "editor",
|
||||
row = row,
|
||||
col = col,
|
||||
width = width,
|
||||
height = height,
|
||||
style = "minimal",
|
||||
border = "rounded",
|
||||
title = " Additional Context Needed ",
|
||||
title_pos = "center",
|
||||
})
|
||||
|
||||
-- Set window options
|
||||
vim.wo[state.win].wrap = true
|
||||
vim.wo[state.win].cursorline = true
|
||||
|
||||
-- Add header showing what the LLM said
|
||||
local header_lines = {
|
||||
"-- LLM Response: --",
|
||||
}
|
||||
|
||||
-- Truncate LLM response for display
|
||||
local response_preview = llm_response or ""
|
||||
if #response_preview > 200 then
|
||||
response_preview = response_preview:sub(1, 200) .. "..."
|
||||
end
|
||||
for line in response_preview:gmatch("[^\n]+") do
|
||||
table.insert(header_lines, "-- " .. line)
|
||||
end
|
||||
|
||||
table.insert(header_lines, "")
|
||||
table.insert(header_lines, "-- Enter additional context below (Ctrl-Enter to submit, Esc to cancel) --")
|
||||
table.insert(header_lines, "")
|
||||
|
||||
vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, header_lines)
|
||||
|
||||
-- Move cursor to the end
|
||||
vim.api.nvim_win_set_cursor(state.win, { #header_lines, 0 })
|
||||
|
||||
-- Set up keymaps
|
||||
local opts = { buffer = state.buf, noremap = true, silent = true }
|
||||
|
||||
-- Submit with Ctrl+Enter or <leader>s
|
||||
vim.keymap.set("n", "<C-CR>", submit, opts)
|
||||
vim.keymap.set("i", "<C-CR>", submit, opts)
|
||||
vim.keymap.set("n", "<leader>s", submit, opts)
|
||||
vim.keymap.set("n", "<CR><CR>", submit, opts)
|
||||
|
||||
-- Close with Esc or q
|
||||
vim.keymap.set("n", "<Esc>", M.close, opts)
|
||||
vim.keymap.set("n", "q", M.close, opts)
|
||||
|
||||
-- Start in insert mode
|
||||
vim.cmd("startinsert")
|
||||
|
||||
-- Log
|
||||
pcall(function()
|
||||
local logs = require("codetyper.agent.logs")
|
||||
logs.add({
|
||||
type = "info",
|
||||
message = "Context modal opened - waiting for user input",
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
--- Check if modal is open
|
||||
---@return boolean
|
||||
function M.is_open()
|
||||
return state.win ~= nil and vim.api.nvim_win_is_valid(state.win)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -231,6 +231,8 @@ function M.create_from_event(event, generated_code, confidence, strategy)
|
||||
created_at = os.time(),
|
||||
intent = event.intent,
|
||||
scope = event.scope,
|
||||
-- Store the prompt tag range so we can delete it after applying
|
||||
prompt_tag_range = event.range,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -312,6 +314,89 @@ function M.mark_rejected(id, reason)
|
||||
return false
|
||||
end
|
||||
|
||||
--- Remove /@ @/ prompt tags from buffer
|
||||
---@param bufnr number Buffer number
|
||||
---@return number Number of tag regions removed
|
||||
local function remove_prompt_tags(bufnr)
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return 0
|
||||
end
|
||||
|
||||
local removed = 0
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
|
||||
-- Find and remove all /@ ... @/ regions (can be multiline)
|
||||
local i = 1
|
||||
while i <= #lines do
|
||||
local line = lines[i]
|
||||
local open_start = line:find("/@")
|
||||
|
||||
if open_start then
|
||||
-- Found an opening tag, look for closing tag
|
||||
local close_end = nil
|
||||
local close_line = i
|
||||
|
||||
-- Check if closing tag is on same line
|
||||
local after_open = line:sub(open_start + 2)
|
||||
local same_line_close = after_open:find("@/")
|
||||
if same_line_close then
|
||||
-- Single line tag - remove just this portion
|
||||
local before = line:sub(1, open_start - 1)
|
||||
local after = line:sub(open_start + 2 + same_line_close + 1)
|
||||
lines[i] = before .. after
|
||||
-- If line is now empty or just whitespace, remove it
|
||||
if lines[i]:match("^%s*$") then
|
||||
table.remove(lines, i)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
removed = removed + 1
|
||||
else
|
||||
-- Multi-line tag - find the closing line
|
||||
for j = i, #lines do
|
||||
if lines[j]:find("@/") then
|
||||
close_line = j
|
||||
close_end = lines[j]:find("@/")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if close_end then
|
||||
-- Remove lines from i to close_line
|
||||
-- Keep content before /@ on first line and after @/ on last line
|
||||
local before = lines[i]:sub(1, open_start - 1)
|
||||
local after = lines[close_line]:sub(close_end + 2)
|
||||
|
||||
-- Remove the lines containing the tag
|
||||
for _ = i, close_line do
|
||||
table.remove(lines, i)
|
||||
end
|
||||
|
||||
-- If there's content to keep, insert it back
|
||||
local remaining = (before .. after):match("^%s*(.-)%s*$")
|
||||
if remaining and remaining ~= "" then
|
||||
table.insert(lines, i, remaining)
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
removed = removed + 1
|
||||
else
|
||||
-- No closing tag found, skip this line
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
if removed > 0 then
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
end
|
||||
|
||||
return removed
|
||||
end
|
||||
|
||||
--- Apply a patch to the target buffer
|
||||
---@param patch PatchCandidate
|
||||
---@return boolean success
|
||||
@@ -349,29 +434,34 @@ function M.apply(patch)
|
||||
-- Prepare code lines
|
||||
local code_lines = vim.split(patch.generated_code, "\n", { plain = true })
|
||||
|
||||
-- FIRST: Remove the prompt tags from the buffer before applying code
|
||||
-- This prevents the infinite loop where tags stay and get re-detected
|
||||
local tags_removed = remove_prompt_tags(target_bufnr)
|
||||
|
||||
pcall(function()
|
||||
if tags_removed > 0 then
|
||||
local logs = require("codetyper.agent.logs")
|
||||
logs.add({
|
||||
type = "info",
|
||||
message = string.format("Removed %d prompt tag(s) from buffer", tags_removed),
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
-- Recalculate line count after tag removal
|
||||
local line_count = vim.api.nvim_buf_line_count(target_bufnr)
|
||||
|
||||
-- Apply based on strategy
|
||||
local ok, err = pcall(function()
|
||||
if patch.injection_strategy == "replace" and patch.injection_range then
|
||||
-- Replace specific range
|
||||
vim.api.nvim_buf_set_lines(
|
||||
target_bufnr,
|
||||
patch.injection_range.start_line - 1,
|
||||
patch.injection_range.end_line,
|
||||
false,
|
||||
code_lines
|
||||
)
|
||||
-- For replace, we need to adjust the range since we removed tags
|
||||
-- Just append to end since the original context might have shifted
|
||||
vim.api.nvim_buf_set_lines(target_bufnr, line_count, line_count, false, code_lines)
|
||||
elseif patch.injection_strategy == "insert" and patch.injection_range then
|
||||
-- Insert at specific line
|
||||
vim.api.nvim_buf_set_lines(
|
||||
target_bufnr,
|
||||
patch.injection_range.start_line - 1,
|
||||
patch.injection_range.start_line - 1,
|
||||
false,
|
||||
code_lines
|
||||
)
|
||||
-- Insert at end since original position might have shifted
|
||||
vim.api.nvim_buf_set_lines(target_bufnr, line_count, line_count, false, code_lines)
|
||||
else
|
||||
-- Default: append to end
|
||||
local line_count = vim.api.nvim_buf_line_count(target_bufnr)
|
||||
vim.api.nvim_buf_set_lines(target_bufnr, line_count, line_count, false, code_lines)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class AttachedFile
|
||||
---@field path string Relative path as referenced in prompt
|
||||
---@field full_path string Absolute path to the file
|
||||
---@field content string File content
|
||||
|
||||
---@class PromptEvent
|
||||
---@field id string Unique event ID
|
||||
---@field bufnr number Source buffer number
|
||||
@@ -16,7 +21,7 @@ local M = {}
|
||||
---@field prompt_content string Cleaned prompt text
|
||||
---@field target_path string Target file for injection
|
||||
---@field priority number Priority (1=high, 2=normal, 3=low)
|
||||
---@field status string "pending"|"processing"|"completed"|"escalated"|"cancelled"
|
||||
---@field status string "pending"|"processing"|"completed"|"escalated"|"cancelled"|"needs_context"|"failed"
|
||||
---@field attempt_count number Number of processing attempts
|
||||
---@field worker_type string|nil LLM provider used ("ollama"|"claude"|etc)
|
||||
---@field created_at number System time when created
|
||||
@@ -24,6 +29,7 @@ local M = {}
|
||||
---@field scope ScopeInfo|nil Resolved scope (function/class/file)
|
||||
---@field scope_text string|nil Text of the resolved scope
|
||||
---@field scope_range {start_line: number, end_line: number}|nil Range of scope in target
|
||||
---@field attached_files AttachedFile[]|nil Files attached via @filename syntax
|
||||
|
||||
--- Internal state
|
||||
---@type PromptEvent[]
|
||||
@@ -383,16 +389,21 @@ function M.clear(status)
|
||||
notify_listeners("update", nil)
|
||||
end
|
||||
|
||||
--- Cleanup completed/cancelled events older than max_age seconds
|
||||
--- Cleanup completed/cancelled/failed events older than max_age seconds
|
||||
---@param max_age number Maximum age in seconds (default: 300)
|
||||
function M.cleanup(max_age)
|
||||
max_age = max_age or 300
|
||||
local now = os.time()
|
||||
local terminal_statuses = {
|
||||
completed = true,
|
||||
cancelled = true,
|
||||
failed = true,
|
||||
needs_context = true,
|
||||
}
|
||||
local i = 1
|
||||
while i <= #queue do
|
||||
local event = queue[i]
|
||||
if (event.status == "completed" or event.status == "cancelled")
|
||||
and (now - event.created_at) > max_age then
|
||||
if terminal_statuses[event.status] and (now - event.created_at) > max_age then
|
||||
table.remove(queue, i)
|
||||
else
|
||||
i = i + 1
|
||||
@@ -410,6 +421,8 @@ function M.stats()
|
||||
completed = 0,
|
||||
cancelled = 0,
|
||||
escalated = 0,
|
||||
failed = 0,
|
||||
needs_context = 0,
|
||||
}
|
||||
for _, event in ipairs(queue) do
|
||||
local s = event.status
|
||||
|
||||
@@ -10,6 +10,7 @@ local queue = require("codetyper.agent.queue")
|
||||
local patch = require("codetyper.agent.patch")
|
||||
local worker = require("codetyper.agent.worker")
|
||||
local confidence_mod = require("codetyper.agent.confidence")
|
||||
local context_modal = require("codetyper.agent.context_modal")
|
||||
|
||||
--- Scheduler state
|
||||
local state = {
|
||||
@@ -118,10 +119,59 @@ local function get_primary_provider()
|
||||
return "claude"
|
||||
end
|
||||
|
||||
--- Retry event with additional context
|
||||
---@param original_event table Original prompt event
|
||||
---@param additional_context string Additional context from user
|
||||
local function retry_with_context(original_event, additional_context)
|
||||
-- Create new prompt content combining original + additional
|
||||
local combined_prompt = string.format(
|
||||
"%s\n\nAdditional context:\n%s",
|
||||
original_event.prompt_content,
|
||||
additional_context
|
||||
)
|
||||
|
||||
-- Create a new event with the combined prompt
|
||||
local new_event = vim.deepcopy(original_event)
|
||||
new_event.id = nil -- Will be assigned a new ID
|
||||
new_event.prompt_content = combined_prompt
|
||||
new_event.attempt_count = 0
|
||||
new_event.status = nil
|
||||
|
||||
-- Log the retry
|
||||
pcall(function()
|
||||
local logs = require("codetyper.agent.logs")
|
||||
logs.add({
|
||||
type = "info",
|
||||
message = string.format("Retrying with additional context (original: %s)", original_event.id),
|
||||
})
|
||||
end)
|
||||
|
||||
-- Queue the new event
|
||||
queue.enqueue(new_event)
|
||||
end
|
||||
|
||||
--- Process worker result and decide next action
|
||||
---@param event table PromptEvent
|
||||
---@param result table WorkerResult
|
||||
local function handle_worker_result(event, result)
|
||||
-- Check if LLM needs more context
|
||||
if result.needs_context then
|
||||
pcall(function()
|
||||
local logs = require("codetyper.agent.logs")
|
||||
logs.add({
|
||||
type = "info",
|
||||
message = string.format("Event %s: LLM needs more context, opening modal", event.id),
|
||||
})
|
||||
end)
|
||||
|
||||
-- Open the context modal
|
||||
context_modal.open(result.original_event or event, result.response or "", retry_with_context)
|
||||
|
||||
-- Mark original event as needing context (not failed)
|
||||
queue.update_status(event.id, "needs_context", { response = result.response })
|
||||
return
|
||||
end
|
||||
|
||||
if not result.success then
|
||||
-- Failed - try escalation if this was ollama
|
||||
if result.worker_type == "ollama" and event.attempt_count < 2 then
|
||||
|
||||
@@ -75,17 +75,43 @@ local block_nodes = {
|
||||
---@param bufnr number
|
||||
---@return boolean
|
||||
function M.has_treesitter(bufnr)
|
||||
local ok, parsers = pcall(require, "nvim-treesitter.parsers")
|
||||
if not ok then
|
||||
return false
|
||||
-- Try to get the language for this buffer
|
||||
local lang = nil
|
||||
|
||||
-- Method 1: Use vim.treesitter (Neovim 0.9+)
|
||||
if vim.treesitter and vim.treesitter.language then
|
||||
local ft = vim.bo[bufnr].filetype
|
||||
if vim.treesitter.language.get_lang then
|
||||
lang = vim.treesitter.language.get_lang(ft)
|
||||
else
|
||||
lang = ft
|
||||
end
|
||||
end
|
||||
|
||||
local lang = parsers.get_buf_lang(bufnr)
|
||||
-- Method 2: Try nvim-treesitter parsers module
|
||||
if not lang then
|
||||
local ok, parsers = pcall(require, "nvim-treesitter.parsers")
|
||||
if ok and parsers then
|
||||
if parsers.get_buf_lang then
|
||||
lang = parsers.get_buf_lang(bufnr)
|
||||
elseif parsers.ft_to_lang then
|
||||
lang = parsers.ft_to_lang(vim.bo[bufnr].filetype)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback to filetype
|
||||
if not lang then
|
||||
lang = vim.bo[bufnr].filetype
|
||||
end
|
||||
|
||||
if not lang or lang == "" then
|
||||
return false
|
||||
end
|
||||
|
||||
return parsers.has_parser(lang)
|
||||
-- Check if parser is available
|
||||
local has_parser = pcall(vim.treesitter.get_parser, bufnr, lang)
|
||||
return has_parser
|
||||
end
|
||||
|
||||
--- Get Tree-sitter node at position
|
||||
|
||||
@@ -31,6 +31,137 @@ local confidence = require("codetyper.agent.confidence")
|
||||
--- Worker ID counter
|
||||
local worker_counter = 0
|
||||
|
||||
--- Patterns that indicate LLM needs more context (must be near start of response)
|
||||
local context_needed_patterns = {
|
||||
"^%s*i need more context",
|
||||
"^%s*i'm sorry.-i need more",
|
||||
"^%s*i apologize.-i need more",
|
||||
"^%s*could you provide more context",
|
||||
"^%s*could you please provide more",
|
||||
"^%s*can you clarify",
|
||||
"^%s*please provide more context",
|
||||
"^%s*more information needed",
|
||||
"^%s*not enough context",
|
||||
"^%s*i don't have enough",
|
||||
"^%s*unclear what you",
|
||||
"^%s*what do you mean by",
|
||||
}
|
||||
|
||||
--- Check if response indicates need for more context
|
||||
--- Only triggers if the response primarily asks for context (no substantial code)
|
||||
---@param response string
|
||||
---@return boolean
|
||||
local function needs_more_context(response)
|
||||
if not response then
|
||||
return false
|
||||
end
|
||||
|
||||
-- If response has substantial code (more than 5 lines with code-like content), don't ask for context
|
||||
local lines = vim.split(response, "\n")
|
||||
local code_lines = 0
|
||||
for _, line in ipairs(lines) do
|
||||
-- Count lines that look like code (have programming constructs)
|
||||
if line:match("[{}();=]") or line:match("function") or line:match("def ")
|
||||
or line:match("class ") or line:match("return ") or line:match("import ")
|
||||
or line:match("public ") or line:match("private ") or line:match("local ") then
|
||||
code_lines = code_lines + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- If there's substantial code, don't trigger context request
|
||||
if code_lines >= 3 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check if the response STARTS with a context-needed phrase
|
||||
local lower = response:lower()
|
||||
for _, pattern in ipairs(context_needed_patterns) do
|
||||
if lower:match(pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Clean LLM response to extract only code
|
||||
---@param response string Raw LLM response
|
||||
---@param filetype string|nil File type for language detection
|
||||
---@return string Cleaned code
|
||||
local function clean_response(response, filetype)
|
||||
if not response then
|
||||
return ""
|
||||
end
|
||||
|
||||
local cleaned = response
|
||||
|
||||
-- Remove the original prompt tags /@ ... @/ if they appear in output
|
||||
-- Use [%s%S] to match any character including newlines (Lua's . doesn't match newlines)
|
||||
cleaned = cleaned:gsub("/@[%s%S]-@/", "")
|
||||
|
||||
-- Try to extract code from markdown code blocks
|
||||
-- Match ```language\n...\n``` or just ```\n...\n```
|
||||
local code_block = cleaned:match("```[%w]*\n(.-)\n```")
|
||||
if not code_block then
|
||||
-- Try without newline after language
|
||||
code_block = cleaned:match("```[%w]*(.-)\n```")
|
||||
end
|
||||
if not code_block then
|
||||
-- Try single line code block
|
||||
code_block = cleaned:match("```(.-)```")
|
||||
end
|
||||
|
||||
if code_block then
|
||||
cleaned = code_block
|
||||
else
|
||||
-- No code block found, try to remove common prefixes/suffixes
|
||||
-- Remove common apology/explanation phrases at the start
|
||||
local explanation_starts = {
|
||||
"^[Ii]'m sorry.-\n",
|
||||
"^[Ii] apologize.-\n",
|
||||
"^[Hh]ere is.-:\n",
|
||||
"^[Hh]ere's.-:\n",
|
||||
"^[Tt]his is.-:\n",
|
||||
"^[Bb]ased on.-:\n",
|
||||
"^[Ss]ure.-:\n",
|
||||
"^[Oo][Kk].-:\n",
|
||||
"^[Cc]ertainly.-:\n",
|
||||
}
|
||||
for _, pattern in ipairs(explanation_starts) do
|
||||
cleaned = cleaned:gsub(pattern, "")
|
||||
end
|
||||
|
||||
-- Remove trailing explanations
|
||||
local explanation_ends = {
|
||||
"\n[Tt]his code.-$",
|
||||
"\n[Tt]his function.-$",
|
||||
"\n[Tt]his is a.-$",
|
||||
"\n[Ii] hope.-$",
|
||||
"\n[Ll]et me know.-$",
|
||||
"\n[Ff]eel free.-$",
|
||||
"\n[Nn]ote:.-$",
|
||||
"\n[Pp]lease replace.-$",
|
||||
"\n[Pp]lease note.-$",
|
||||
"\n[Yy]ou might want.-$",
|
||||
"\n[Yy]ou may want.-$",
|
||||
"\n[Mm]ake sure.-$",
|
||||
"\n[Aa]lso,.-$",
|
||||
"\n[Rr]emember.-$",
|
||||
}
|
||||
for _, pattern in ipairs(explanation_ends) do
|
||||
cleaned = cleaned:gsub(pattern, "")
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove any remaining markdown artifacts
|
||||
cleaned = cleaned:gsub("^```[%w]*\n?", "")
|
||||
cleaned = cleaned:gsub("\n?```$", "")
|
||||
|
||||
-- Trim whitespace
|
||||
cleaned = cleaned:match("^%s*(.-)%s*$") or cleaned
|
||||
|
||||
return cleaned
|
||||
end
|
||||
|
||||
--- Active workers
|
||||
---@type table<string, Worker>
|
||||
local active_workers = {}
|
||||
@@ -63,6 +194,28 @@ local function get_client(worker_type)
|
||||
return nil, "Unknown provider: " .. worker_type
|
||||
end
|
||||
|
||||
--- Format attached files for inclusion in prompt
|
||||
---@param attached_files table[]|nil
|
||||
---@return string
|
||||
local function format_attached_files(attached_files)
|
||||
if not attached_files or #attached_files == 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local parts = { "\n\n--- Referenced Files ---" }
|
||||
for _, file in ipairs(attached_files) do
|
||||
local ext = vim.fn.fnamemodify(file.path, ":e")
|
||||
table.insert(parts, string.format(
|
||||
"\n\nFile: %s\n```%s\n%s\n```",
|
||||
file.path,
|
||||
ext,
|
||||
file.content:sub(1, 3000) -- Limit each file to 3000 chars
|
||||
))
|
||||
end
|
||||
|
||||
return table.concat(parts, "")
|
||||
end
|
||||
|
||||
--- Build prompt for code generation
|
||||
---@param event table PromptEvent
|
||||
---@return string prompt
|
||||
@@ -83,6 +236,9 @@ local function build_prompt(event)
|
||||
|
||||
local filetype = vim.fn.fnamemodify(event.target_path or "", ":e")
|
||||
|
||||
-- Format attached files
|
||||
local attached_content = format_attached_files(event.attached_files)
|
||||
|
||||
-- Build context with scope information
|
||||
local context = {
|
||||
target_path = event.target_path,
|
||||
@@ -92,6 +248,7 @@ local function build_prompt(event)
|
||||
scope_text = event.scope_text,
|
||||
scope_range = event.scope_range,
|
||||
intent = event.intent,
|
||||
attached_files = event.attached_files,
|
||||
}
|
||||
|
||||
-- Build the actual prompt based on intent and scope
|
||||
@@ -115,7 +272,7 @@ local function build_prompt(event)
|
||||
```%s
|
||||
%s
|
||||
```
|
||||
|
||||
%s
|
||||
User request: %s
|
||||
|
||||
Return the complete transformed %s. Output only code, no explanations.]],
|
||||
@@ -124,6 +281,7 @@ Return the complete transformed %s. Output only code, no explanations.]],
|
||||
filetype,
|
||||
filetype,
|
||||
event.scope_text,
|
||||
attached_content,
|
||||
event.prompt_content,
|
||||
scope_type
|
||||
)
|
||||
@@ -135,7 +293,7 @@ Return the complete transformed %s. Output only code, no explanations.]],
|
||||
```%s
|
||||
%s
|
||||
```
|
||||
|
||||
%s
|
||||
User request: %s
|
||||
|
||||
Output only the code to insert, no explanations.]],
|
||||
@@ -143,6 +301,7 @@ Output only the code to insert, no explanations.]],
|
||||
scope_name,
|
||||
filetype,
|
||||
event.scope_text,
|
||||
attached_content,
|
||||
event.prompt_content
|
||||
)
|
||||
end
|
||||
@@ -154,7 +313,7 @@ Output only the code to insert, no explanations.]],
|
||||
```%s
|
||||
%s
|
||||
```
|
||||
|
||||
%s
|
||||
User request: %s
|
||||
|
||||
Output only code, no explanations.]],
|
||||
@@ -162,6 +321,7 @@ Output only code, no explanations.]],
|
||||
filetype,
|
||||
filetype,
|
||||
target_content:sub(1, 4000), -- Limit context size
|
||||
attached_content,
|
||||
event.prompt_content
|
||||
)
|
||||
end
|
||||
@@ -303,8 +463,40 @@ function M.complete(worker, response, error, usage)
|
||||
return
|
||||
end
|
||||
|
||||
-- Score confidence
|
||||
local conf_score, breakdown = confidence.score(response, worker.event.prompt_content)
|
||||
-- Check if LLM needs more context
|
||||
if needs_more_context(response) then
|
||||
worker.status = "needs_context"
|
||||
active_workers[worker.id] = nil
|
||||
|
||||
pcall(function()
|
||||
local logs = require("codetyper.agent.logs")
|
||||
logs.add({
|
||||
type = "info",
|
||||
message = string.format("Worker %s: LLM needs more context", worker.id),
|
||||
})
|
||||
end)
|
||||
|
||||
worker.callback({
|
||||
success = false,
|
||||
response = response,
|
||||
error = nil,
|
||||
needs_context = true,
|
||||
original_event = worker.event,
|
||||
confidence = 0,
|
||||
confidence_breakdown = {},
|
||||
duration = duration,
|
||||
worker_type = worker.worker_type,
|
||||
usage = usage,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Clean the response (remove markdown, explanations, etc.)
|
||||
local filetype = vim.fn.fnamemodify(worker.event.target_path or "", ":e")
|
||||
local cleaned_response = clean_response(response, filetype)
|
||||
|
||||
-- Score confidence on cleaned response
|
||||
local conf_score, breakdown = confidence.score(cleaned_response, worker.event.prompt_content)
|
||||
|
||||
worker.status = "completed"
|
||||
active_workers[worker.id] = nil
|
||||
@@ -326,7 +518,7 @@ function M.complete(worker, response, error, usage)
|
||||
|
||||
worker.callback({
|
||||
success = true,
|
||||
response = response,
|
||||
response = cleaned_response,
|
||||
error = nil,
|
||||
confidence = conf_score,
|
||||
confidence_breakdown = breakdown,
|
||||
|
||||
Reference in New Issue
Block a user