2024-08-17 16:04:40 -04:00
local api = vim.api
2024-08-15 19:04:15 +08:00
local curl = require ( " plenary.curl " )
2024-08-17 15:14:30 +08:00
local Utils = require ( " avante.utils " )
local Config = require ( " avante.config " )
2024-09-03 04:09:13 -04:00
local Path = require ( " avante.path " )
2024-08-22 01:48:40 -04:00
local P = require ( " avante.providers " )
2024-08-18 15:03:25 -04:00
2024-08-18 22:20:29 -04:00
---@class avante.LLM
2024-08-17 15:14:30 +08:00
local M = { }
2024-08-15 19:04:15 +08:00
2024-08-18 22:20:29 -04:00
M.CANCEL_PATTERN = " AvanteLLMEscape "
2024-08-18 15:03:25 -04:00
------------------------------Prompt and type------------------------------
2024-08-26 22:31:57 -04:00
local group = api.nvim_create_augroup ( " avante_llm " , { clear = true } )
2024-08-18 15:03:25 -04:00
2024-09-03 04:09:13 -04:00
---@alias LlmMode "planning" | "editing" | "suggesting"
---
---@class TemplateOptions
---@field use_xml_format boolean
---@field ask boolean
---@field question string
2024-08-30 22:21:50 +08:00
---@field code_lang string
2024-09-03 04:09:13 -04:00
---@field file_content string
2024-08-30 18:53:49 +08:00
---@field selected_code string | nil
---@field project_context string | nil
---@field memory_context string | nil
2024-09-03 04:09:13 -04:00
---
---@class StreamOptions: TemplateOptions
---@field bufnr integer
---@field instructions string
---@field mode LlmMode
2024-08-30 18:53:49 +08:00
---@field on_chunk AvanteChunkParser
---@field on_complete AvanteCompleteParser
---@param opts StreamOptions
M.stream = function ( opts )
local mode = opts.mode or " planning "
2024-09-03 04:09:13 -04:00
---@type AvanteProviderFunctor
local Provider = P [ Config.provider ]
2024-08-30 18:53:49 +08:00
2024-08-30 22:21:50 +08:00
-- Check if the instructions contains an image path
2024-08-30 18:53:49 +08:00
local image_paths = { }
local original_instructions = opts.instructions
if opts.instructions : match ( " image: " ) then
local lines = vim.split ( opts.instructions , " \n " )
2024-08-28 14:43:14 -04:00
for i , line in ipairs ( lines ) do
if line : match ( " ^image: " ) then
2024-08-30 18:53:49 +08:00
local image_path = line : gsub ( " ^image: " , " " )
table.insert ( image_paths , image_path )
2024-08-28 14:43:14 -04:00
table.remove ( lines , i )
end
end
2024-08-30 18:53:49 +08:00
original_instructions = table.concat ( lines , " \n " )
2024-08-28 14:43:14 -04:00
end
2024-09-03 04:09:13 -04:00
Path.prompts . initialize ( Path.prompts . get ( opts.bufnr ) )
local user_prompt = Path.prompts . render ( mode , {
use_xml_format = Provider.use_xml_format ,
ask = true , -- TODO: add mode without ask instruction
question = original_instructions ,
code_lang = opts.code_lang ,
file_content = opts.file_content ,
selected_code = opts.selected_code ,
project_context = opts.project_context ,
memory_context = opts.memory_context ,
} )
2024-08-30 22:21:50 +08:00
2024-09-03 04:09:13 -04:00
Utils.debug ( user_prompt )
2024-08-30 18:53:49 +08:00
2024-08-22 01:48:40 -04:00
---@type AvantePromptOptions
2024-08-18 22:20:29 -04:00
local code_opts = {
2024-09-03 04:09:13 -04:00
system_prompt = Config.system_prompt ,
user_prompt = user_prompt ,
2024-08-30 18:53:49 +08:00
image_paths = image_paths ,
2024-08-18 22:20:29 -04:00
}
2024-08-22 01:48:40 -04:00
---@type string
local current_event_state = nil
2024-08-18 22:20:29 -04:00
2024-08-22 01:48:40 -04:00
---@type AvanteHandlerOptions
2024-08-30 18:53:49 +08:00
local handler_opts = { on_chunk = opts.on_chunk , on_complete = opts.on_complete }
2024-08-22 01:48:40 -04:00
---@type AvanteCurlOutput
2024-08-24 17:52:38 -04:00
local spec = Provider.parse_curl_args ( Provider , code_opts )
2024-08-18 15:03:25 -04:00
2024-08-27 06:57:29 -04:00
Utils.debug ( spec )
2024-08-18 15:03:25 -04:00
---@param line string
2024-08-22 01:48:40 -04:00
local function parse_stream_data ( line )
2024-08-18 15:03:25 -04:00
local event = line : match ( " ^event: (.+)$ " )
if event then
2024-08-19 06:11:02 -04:00
current_event_state = event
2024-08-18 15:03:25 -04:00
return
end
local data_match = line : match ( " ^data: (.+)$ " )
if data_match then
2024-08-22 01:48:40 -04:00
Provider.parse_response ( data_match , current_event_state , handler_opts )
2024-08-18 15:03:25 -04:00
end
end
2024-08-28 22:17:00 +08:00
local completed = false
2024-09-03 14:03:59 +08:00
local active_job
2024-08-18 15:03:25 -04:00
active_job = curl.post ( spec.url , {
headers = spec.headers ,
2024-08-22 01:48:40 -04:00
proxy = spec.proxy ,
insecure = spec.insecure ,
2024-08-18 15:03:25 -04:00
body = vim.json . encode ( spec.body ) ,
stream = function ( err , data , _ )
2024-08-15 19:04:15 +08:00
if err then
2024-08-28 22:17:00 +08:00
completed = true
2024-08-30 18:53:49 +08:00
opts.on_complete ( err )
2024-08-15 19:04:15 +08:00
return
end
if not data then
return
end
2024-08-18 15:03:25 -04:00
vim.schedule ( function ( )
2024-09-03 04:09:13 -04:00
if Config.options [ Config.provider ] == nil and Provider.parse_stream_data ~= nil then
2024-08-22 01:48:40 -04:00
if Provider.parse_response ~= nil then
2024-08-20 07:54:58 -04:00
Utils.warn (
" parse_stream_data and parse_response_data are mutually exclusive, and thus parse_response_data will be ignored. Make sure that you handle the incoming data correctly. " ,
{ once = true }
)
end
2024-08-22 01:48:40 -04:00
Provider.parse_stream_data ( data , handler_opts )
2024-08-20 07:54:58 -04:00
else
2024-08-23 09:36:40 -04:00
if Provider.parse_stream_data ~= nil then
Provider.parse_stream_data ( data , handler_opts )
else
parse_stream_data ( data )
end
2024-08-20 07:54:58 -04:00
end
2024-08-18 15:03:25 -04:00
end )
end ,
2024-09-03 14:03:59 +08:00
on_error = function ( )
active_job = nil
2024-08-28 22:17:00 +08:00
completed = true
2024-09-03 14:03:59 +08:00
opts.on_complete ( nil )
2024-08-18 15:03:25 -04:00
end ,
2024-08-25 21:26:19 -04:00
callback = function ( result )
2024-09-03 14:03:59 +08:00
active_job = nil
2024-08-25 21:26:19 -04:00
if result.status >= 400 then
if Provider.on_error then
Provider.on_error ( result )
else
Utils.error ( " API request failed with status " .. result.status , { once = true , title = " Avante " } )
end
2024-08-28 22:17:00 +08:00
vim.schedule ( function ( )
if not completed then
completed = true
2024-08-30 18:53:49 +08:00
opts.on_complete (
" API request failed with status " .. result.status .. " . Body: " .. vim.inspect ( result.body )
)
2024-08-28 22:17:00 +08:00
end
end )
2024-08-25 21:26:19 -04:00
end
2024-08-18 15:03:25 -04:00
end ,
} )
api.nvim_create_autocmd ( " User " , {
group = group ,
pattern = M.CANCEL_PATTERN ,
2024-09-03 14:03:59 +08:00
once = true ,
2024-08-18 15:03:25 -04:00
callback = function ( )
2024-09-03 14:03:59 +08:00
-- Error: cannot resume dead coroutine
2024-08-18 15:03:25 -04:00
if active_job then
2024-09-03 14:03:59 +08:00
xpcall ( function ( )
active_job : shutdown ( )
end , function ( err )
return err
end )
2024-08-19 05:40:57 -04:00
Utils.debug ( " LLM request cancelled " , { title = " Avante " } )
2024-08-18 15:03:25 -04:00
active_job = nil
2024-08-15 19:04:15 +08:00
end
end ,
} )
2024-08-18 15:03:25 -04:00
return active_job
2024-08-15 19:04:15 +08:00
end
2024-09-03 14:03:59 +08:00
function M . cancel_inflight_request ( )
api.nvim_exec_autocmds ( " User " , { pattern = M.CANCEL_PATTERN } )
end
2024-08-20 07:43:53 -04:00
return M