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,111 +10,109 @@ 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
function M.get_response(prompt, context, callback) -- Message handling functions
local api_key = get_api_key() local Message = {}
if not api_key then function Message.create_contents(prompt, context)
vim.schedule(function()
callback(nil, "API key not set")
end)
return
end
local model = "gemini-2.0-flash"
local contents = {} local contents = {}
-- Add conversation history -- Add conversation history
for _, message in ipairs(conversation_history) do for _, message in ipairs(conversation_history) do
table.insert(contents, { table.insert(contents, {
role = message.role, role = message.role,
parts = {{ parts = {{text = message.content}}
text = message.content
}}
}) })
end end
@ -122,41 +120,23 @@ function M.get_response(prompt, context, callback)
if context then if context then
table.insert(contents, { table.insert(contents, {
role = "user", role = "user",
parts = {{ parts = {{text = "Context:\n" .. context .. "\n\nQuery: " .. prompt}}
text = "Context:\n" .. context .. "\n\nQuery: " .. prompt
}}
}) })
else else
table.insert(contents, { table.insert(contents, {
role = "user", role = "user",
parts = {{ parts = {{text = prompt}}
text = prompt
}}
}) })
end end
-- Store prompt in history return contents
table.insert(conversation_history, {
role = "user",
content = prompt
})
local payload = vim.json.encode({
contents = contents,
})
local url = string.format(
"https://generativelanguage.googleapis.com/v1/models/%s:generateContent?key=%s",
model,
api_key
)
async_request(url, payload, function(result, error)
if error then
callback(nil, error)
return
end 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 if result.error then
callback(nil, "API Error: " .. vim.inspect(result.error)) callback(nil, "API Error: " .. vim.inspect(result.error))
return return
@ -170,21 +150,45 @@ function M.get_response(prompt, context, callback)
result.candidates[1].content.parts[1].text then result.candidates[1].content.parts[1].text then
local response_text = result.candidates[1].content.parts[1].text local response_text = result.candidates[1].content.parts[1].text
Message.store_message("model", response_text)
-- Store response in history
table.insert(conversation_history, {
role = "model",
content = response_text
})
callback(response_text) callback(response_text)
else else
callback(nil, "Unexpected response structure") callback(nil, "Unexpected response structure")
end end
end
-- Main API functions
function M.get_response(prompt, context, callback)
local api_key = get_api_key()
if not api_key then
vim.schedule(function()
callback(nil, "API key not set")
end)
return
end
-- Store prompt in history
Message.store_message("user", prompt)
local contents = Message.create_contents(prompt, context)
local payload = vim.json.encode({contents = contents})
local url = string.format(
"https://generativelanguage.googleapis.com/v1/models/%s:generateContent?key=%s",
"gemini-2.0-flash",
api_key
)
local request = Request.new(url, payload)
request:execute(function(result, error)
if error then
callback(nil, error)
return
end
Message.handle_response(result, callback)
end) end)
end end
-- Add function to clear conversation history
function M.clear_conversation() function M.clear_conversation()
conversation_history = {} conversation_history = {}
end end