lua-http #1
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user