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