lua-http #1
@@ -10,111 +10,109 @@ local function get_api_key()
 | 
			
		||||
	return vim.g.gemini_api_key or os.getenv("GEMINI_API_KEY")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- Async HTTP request function
 | 
			
		||||
local function async_request(url, payload, callback)
 | 
			
		||||
	local response = ""
 | 
			
		||||
	local error_msg = ""
 | 
			
		||||
-- Create a Request class to handle HTTP requests
 | 
			
		||||
local Request = {}
 | 
			
		||||
Request.__index = Request
 | 
			
		||||
 | 
			
		||||
	-- Create pipes for stdout and stderr
 | 
			
		||||
	local stdout = vim.loop.new_pipe()
 | 
			
		||||
	local stderr = vim.loop.new_pipe()
 | 
			
		||||
function Request.new(url, payload)
 | 
			
		||||
	local self = setmetatable({}, Request)
 | 
			
		||||
	self.url = url
 | 
			
		||||
	self.payload = payload
 | 
			
		||||
	self.response = ""
 | 
			
		||||
	self.error_msg = ""
 | 
			
		||||
	self.stdout = vim.loop.new_pipe()
 | 
			
		||||
	self.stderr = vim.loop.new_pipe()
 | 
			
		||||
	self.handle = nil
 | 
			
		||||
	return self
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
	-- Spawn curl process with handle in broader scope
 | 
			
		||||
	local handle
 | 
			
		||||
	local function cleanup()
 | 
			
		||||
		if stdout then stdout:close() end
 | 
			
		||||
		if stderr then stderr:close() end
 | 
			
		||||
		if handle then handle:close() end
 | 
			
		||||
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 = vim.loop.spawn('curl', {
 | 
			
		||||
	-- 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 = {
 | 
			
		||||
			'-s',
 | 
			
		||||
			'-X', 'POST',
 | 
			
		||||
			'-H', 'Content-Type: application/json',
 | 
			
		||||
			'-d', payload,
 | 
			
		||||
			url
 | 
			
		||||
			'-d', self.payload,
 | 
			
		||||
			self.url
 | 
			
		||||
		},
 | 
			
		||||
		stdio = {nil, stdout, stderr}
 | 
			
		||||
	}, function(exit_code, signal)  -- This is the exit callback
 | 
			
		||||
		-- Clean up handles
 | 
			
		||||
		cleanup()
 | 
			
		||||
		stdio = {nil, self.stdout, self.stderr}
 | 
			
		||||
	}, function(exit_code)
 | 
			
		||||
		self:cleanup()
 | 
			
		||||
 | 
			
		||||
		if exit_code ~= 0 then
 | 
			
		||||
			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)
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- 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()
 | 
			
		||||
			if success then
 | 
			
		||||
				callback(decoded)
 | 
			
		||||
			else
 | 
			
		||||
				callback(nil, "JSON decode error: " .. response)
 | 
			
		||||
				callback(nil, "JSON decode error: " .. self.response)
 | 
			
		||||
			end
 | 
			
		||||
		end)
 | 
			
		||||
	end)
 | 
			
		||||
 | 
			
		||||
	if not handle then
 | 
			
		||||
		cleanup()
 | 
			
		||||
		vim.schedule(function()
 | 
			
		||||
			callback(nil, "Failed to start curl")
 | 
			
		||||
		end)
 | 
			
		||||
	if not self.handle then
 | 
			
		||||
		self:handle_error(callback, "Failed to start curl")
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- Handle stdout data
 | 
			
		||||
	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)
 | 
			
		||||
	self:setup_pipes(callback)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function M.get_response(prompt, context, callback)
 | 
			
		||||
	local api_key = get_api_key()
 | 
			
		||||
-- Message handling functions
 | 
			
		||||
local Message = {}
 | 
			
		||||
 | 
			
		||||
	if not api_key then
 | 
			
		||||
		vim.schedule(function()
 | 
			
		||||
			callback(nil, "API key not set")
 | 
			
		||||
		end)
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local model = "gemini-2.0-flash"
 | 
			
		||||
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
 | 
			
		||||
			}}
 | 
			
		||||
			parts = {{text = message.content}}
 | 
			
		||||
		})
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
@@ -122,41 +120,23 @@ function M.get_response(prompt, context, callback)
 | 
			
		||||
	if context then
 | 
			
		||||
		table.insert(contents, {
 | 
			
		||||
			role = "user",
 | 
			
		||||
			parts = {{
 | 
			
		||||
				text = "Context:\n" .. context .. "\n\nQuery: " .. prompt
 | 
			
		||||
			}}
 | 
			
		||||
			parts = {{text = "Context:\n" .. context .. "\n\nQuery: " .. prompt}}
 | 
			
		||||
		})
 | 
			
		||||
	else
 | 
			
		||||
		table.insert(contents, {
 | 
			
		||||
			role = "user",
 | 
			
		||||
			parts = {{
 | 
			
		||||
				text = prompt
 | 
			
		||||
			}}
 | 
			
		||||
			parts = {{text = prompt}}
 | 
			
		||||
		})
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- Store prompt in history
 | 
			
		||||
	table.insert(conversation_history, {
 | 
			
		||||
		role = "user",
 | 
			
		||||
		content = prompt
 | 
			
		||||
	})
 | 
			
		||||
	return contents
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
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
 | 
			
		||||
@@ -170,21 +150,45 @@ function M.get_response(prompt, context, callback)
 | 
			
		||||
	   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
 | 
			
		||||
			})
 | 
			
		||||
			
 | 
			
		||||
		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)
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
-- Add function to clear conversation history
 | 
			
		||||
function M.clear_conversation()
 | 
			
		||||
	conversation_history = {}
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user