lua-http #1

Merged
jwiden merged 9 commits from lua-http into main 2025-03-16 17:54:13 +00:00
Showing only changes of commit 9e2bc5e1e4 - Show all commits

View File

@ -10,91 +10,154 @@ local function get_api_key()
return vim.g.gemini_api_key or os.getenv("GEMINI_API_KEY") return vim.g.gemini_api_key or os.getenv("GEMINI_API_KEY")
end end
-- Async HTTP request function -- Create a Request class to handle HTTP requests
local function async_request(url, payload, callback) local Request = {}
local response = "" Request.__index = Request
local error_msg = ""
-- Create pipes for stdout and stderr function Request.new(url, payload)
local stdout = vim.loop.new_pipe() local self = setmetatable({}, Request)
local stderr = vim.loop.new_pipe() self.url = url
self.payload = payload
-- Spawn curl process with handle in broader scope self.response = ""
local handle self.error_msg = ""
local function cleanup() self.stdout = vim.loop.new_pipe()
if stdout then stdout:close() end self.stderr = vim.loop.new_pipe()
if stderr then stderr:close() end self.handle = nil
if handle then handle:close() end return self
end end
handle = vim.loop.spawn('curl', { function Request:cleanup()
if self.stdout then self.stdout:close() end
if self.stderr then self.stderr:close() end
if self.handle then self.handle:close() end
end
function Request:handle_error(callback, msg)
self:cleanup()
vim.schedule(function()
callback(nil, msg)
end)
end
function Request:setup_pipes(callback)
-- Handle stdout data
self.stdout:read_start(function(err, chunk)
if err then
self:handle_error(callback, "Read error: " .. err)
return
end
if chunk then
self.response = self.response .. chunk
end
end)
-- Handle stderr data
self.stderr:read_start(function(err, chunk)
if err then
self:handle_error(callback, "Error reading stderr: " .. err)
return
end
if chunk then
self.error_msg = self.error_msg .. chunk
end
end)
end
function Request:execute(callback)
self.handle = vim.loop.spawn('curl', {
args = { args = {
'-s', '-s',
'-X', 'POST', '-X', 'POST',
'-H', 'Content-Type: application/json', '-H', 'Content-Type: application/json',
'-d', payload, '-d', self.payload,
url self.url
}, },
stdio = {nil, stdout, stderr} stdio = {nil, self.stdout, self.stderr}
}, function(exit_code, signal) -- This is the exit callback }, function(exit_code)
-- Clean up handles self:cleanup()
cleanup()
if exit_code ~= 0 then if exit_code ~= 0 then
vim.schedule(function() vim.schedule(function()
callback(nil, "Curl failed with code " .. exit_code .. ": " .. error_msg) callback(nil, "Curl failed with code " .. exit_code .. ": " .. self.error_msg)
end) end)
return return
end end
-- Try to parse the response -- Try to parse the response
local success, decoded = pcall(vim.json.decode, response) local success, decoded = pcall(vim.json.decode, self.response)
vim.schedule(function() vim.schedule(function()
if success then if success then
callback(decoded) callback(decoded)
else else
callback(nil, "JSON decode error: " .. response) callback(nil, "JSON decode error: " .. self.response)
end end
end) end)
end) end)
if not handle then if not self.handle then
cleanup() self:handle_error(callback, "Failed to start curl")
vim.schedule(function()
callback(nil, "Failed to start curl")
end)
return return
end end
-- Handle stdout data self:setup_pipes(callback)
stdout:read_start(function(err, chunk)
if err then
cleanup()
vim.schedule(function()
callback(nil, "Read error: " .. err)
end)
return
end
if chunk then
response = response .. chunk
end
end)
-- Handle stderr data
stderr:read_start(function(err, chunk)
if err then
cleanup()
vim.schedule(function()
callback(nil, "Error reading stderr: " .. err)
end)
return
end
if chunk then
error_msg = error_msg .. chunk
end
end)
end end
-- Message handling functions
local Message = {}
function Message.create_contents(prompt, context)
local contents = {}
-- Add conversation history
for _, message in ipairs(conversation_history) do
table.insert(contents, {
role = message.role,
parts = {{text = message.content}}
})
end
-- Add current prompt
if context then
table.insert(contents, {
role = "user",
parts = {{text = "Context:\n" .. context .. "\n\nQuery: " .. prompt}}
})
else
table.insert(contents, {
role = "user",
parts = {{text = prompt}}
})
end
return contents
end
function Message.store_message(role, content)
table.insert(conversation_history, {role = role, content = content})
end
function Message.handle_response(result, callback)
if result.error then
callback(nil, "API Error: " .. vim.inspect(result.error))
return
end
if result.candidates and
result.candidates[1] and
result.candidates[1].content and
result.candidates[1].content.parts and
result.candidates[1].content.parts[1] and
result.candidates[1].content.parts[1].text then
local response_text = result.candidates[1].content.parts[1].text
Message.store_message("model", response_text)
callback(response_text)
else
callback(nil, "Unexpected response structure")
end
end
-- Main API functions
function M.get_response(prompt, context, callback) function M.get_response(prompt, context, callback)
local api_key = get_api_key() local api_key = get_api_key()
@ -105,86 +168,27 @@ function M.get_response(prompt, context, callback)
return return
end end
local model = "gemini-2.0-flash"
local contents = {}
-- Add conversation history
for _, message in ipairs(conversation_history) do
table.insert(contents, {
role = message.role,
parts = {{
text = message.content
}}
})
end
-- Add current prompt
if context then
table.insert(contents, {
role = "user",
parts = {{
text = "Context:\n" .. context .. "\n\nQuery: " .. prompt
}}
})
else
table.insert(contents, {
role = "user",
parts = {{
text = prompt
}}
})
end
-- Store prompt in history -- Store prompt in history
table.insert(conversation_history, { Message.store_message("user", prompt)
role = "user",
content = prompt
})
local payload = vim.json.encode({
contents = contents,
})
local contents = Message.create_contents(prompt, context)
local payload = vim.json.encode({contents = contents})
local url = string.format( local url = string.format(
"https://generativelanguage.googleapis.com/v1/models/%s:generateContent?key=%s", "https://generativelanguage.googleapis.com/v1/models/%s:generateContent?key=%s",
model, "gemini-2.0-flash",
api_key api_key
) )
async_request(url, payload, function(result, error) local request = Request.new(url, payload)
request:execute(function(result, error)
if error then if error then
callback(nil, error) callback(nil, error)
return return
end end
Message.handle_response(result, callback)
if result.error then
callback(nil, "API Error: " .. vim.inspect(result.error))
return
end
if result.candidates and
result.candidates[1] and
result.candidates[1].content and
result.candidates[1].content.parts and
result.candidates[1].content.parts[1] and
result.candidates[1].content.parts[1].text then
local response_text = result.candidates[1].content.parts[1].text
-- Store response in history
table.insert(conversation_history, {
role = "model",
content = response_text
})
callback(response_text)
else
callback(nil, "Unexpected response structure")
end
end) end)
end end
-- Add function to clear conversation history
function M.clear_conversation() function M.clear_conversation()
conversation_history = {} conversation_history = {}
end end