From 890806cf0bf71b848d3fae498eac901286d435f4 Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@archlinux.localdomain>
Date: Wed, 20 Nov 2024 22:13:12 +0800
Subject: [PATCH 1/8] fix: remove highlight over generated text.

---
 lua/gp/dispatcher.lua | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index 26fc76a..cdfadbb 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -511,9 +511,9 @@ D.create_handler = function(buf, win, line, first_undojoin, prefix, cursor)
 		vim.api.nvim_buf_set_lines(buf, first_line + finished_lines, first_line + finished_lines, false, unfinished_lines)
 
 		local new_finished_lines = math.max(0, #lines - 1)
-		for i = finished_lines, new_finished_lines do
-			vim.api.nvim_buf_add_highlight(buf, qt.ns_id, hl_handler_group, first_line + i, 0, -1)
-		end
+		-- for i = finished_lines, new_finished_lines do
+		-- 	vim.api.nvim_buf_add_highlight(buf, qt.ns_id, hl_handler_group, first_line + i, 0, -1)
+		-- end
 		finished_lines = new_finished_lines
 
 		local end_line = first_line + #vim.split(response, "\n")

From e06c01856c2754446391f114794a7877c2bcdf03 Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Tue, 21 Jan 2025 11:25:31 +0800
Subject: [PATCH 2/8] fix: remove default gpt4o models.

---
 lua/gp/config.lua | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lua/gp/config.lua b/lua/gp/config.lua
index c80c3c5..5fb3c7f 100644
--- a/lua/gp/config.lua
+++ b/lua/gp/config.lua
@@ -29,7 +29,7 @@ local config = {
 		-- secret : "sk-...",
 		-- secret = os.getenv("env_name.."),
 		openai = {
-			disable = false,
+			disable = true,
 			endpoint = "https://api.openai.com/v1/chat/completions",
 			-- secret = os.getenv("OPENAI_API_KEY"),
 		},
@@ -103,6 +103,7 @@ local config = {
 			disable = true,
 		},
 		{
+			provider = "openai",
 			name = "ChatGPT4o",
 			chat = true,
 			command = false,

From ac26099eecdcea898a12d94cfe2977de4b20cdb2 Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Sun, 26 Jan 2025 16:11:16 +0800
Subject: [PATCH 3/8] feat: add CoT support for DeepSeek-R1

---
 lua/gp/dispatcher.lua | 43 ++++++++++++++++++++++++++++----
 lua/gp/init.lua       | 58 +++++++++++++++++++++++++++++++------------
 2 files changed, 80 insertions(+), 21 deletions(-)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index cdfadbb..19a2348 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -187,6 +187,19 @@ D.prepare_payload = function(messages, model, provider)
 		output.stream = false
 	end
 
+	if provider == "deepseek" and model.model == "deepseek-reasoner" then
+		for i = #messages, 1, -1 do
+			if messages[i].role == "system" then
+				table.remove(messages, i)
+			end
+		end
+		-- remove max_tokens, top_p, temperature for reason model
+		output.max_tokens = nil
+		output.temperature = nil
+		output.top_p = nil
+		output.stream = true
+	end
+
 	return output
 end
 
@@ -198,6 +211,7 @@ end
 ---@param on_exit function | nil # optional on_exit handler
 ---@param callback function | nil # optional callback handler
 local query = function(buf, provider, payload, handler, on_exit, callback)
+	local is_reasoning = payload.model == "deepseek-reasoner"
 	-- make sure handler is a function
 	if type(handler) ~= "function" then
 		logger.error(
@@ -238,9 +252,15 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 					qt.raw_response = qt.raw_response .. line .. "\n"
 				end
 				line = line:gsub("^data: ", "")
+
 				local content = ""
+				local reasoning_content = ""
+
 				if line:match("choices") and line:match("delta") and line:match("content") then
 					line = vim.json.decode(line)
+					if line.choices[1] and line.choices[1].delta and line.choices[1].delta.reasoning_content then
+						reasoning_content = line.choices[1].delta.reasoning_content
+					end
 					if line.choices[1] and line.choices[1].delta and line.choices[1].delta.content then
 						content = line.choices[1].delta.content
 					end
@@ -264,10 +284,16 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 					end
 				end
 
-
-				if content and type(content) == "string" then
+				if reasoning_content ~= "" and type(reasoning_content) == "string" then
+					handler(qid, reasoning_content, true)
+				elseif content ~= "" and type(content) == "string" then
+					if is_reasoning then
+						handler(qid, "\n", true)
+						handler(qid, "\n</details><!-- }}} -->\n\n", false)
+						is_reasoning = false
+					end
 					qt.response = qt.response .. content
-					handler(qid, content)
+					handler(qid, content, false)
 				end
 			end
 		end
@@ -393,7 +419,7 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 	end
 
 	local temp_file = D.query_dir ..
-		"/" .. logger.now() .. "." .. string.format("%x", math.random(0, 0xFFFFFF)) .. ".json"
+					"/" .. logger.now() .. "." .. string.format("%x", math.random(0, 0xFFFFFF)) .. ".json"
 	helpers.table_to_file(payload, temp_file)
 
 	local curl_params = vim.deepcopy(D.config.curl_params or {})
@@ -463,7 +489,7 @@ D.create_handler = function(buf, win, line, first_undojoin, prefix, cursor)
 	})
 
 	local response = ""
-	return vim.schedule_wrap(function(qid, chunk)
+	return vim.schedule_wrap(function(qid, chunk, is_reasoning)
 		local qt = tasker.get_query(qid)
 		if not qt then
 			return
@@ -503,6 +529,13 @@ D.create_handler = function(buf, win, line, first_undojoin, prefix, cursor)
 			lines[i] = prefix .. l
 		end
 
+		-- prepend prefix > to each line inside CoT
+		if is_reasoning then
+			for i, l in ipairs(lines) do
+				lines[i] = "> " .. l
+			end
+		end
+
 		local unfinished_lines = {}
 		for i = finished_lines + 1, #lines do
 			table.insert(unfinished_lines, lines[i])
diff --git a/lua/gp/init.lua b/lua/gp/init.lua
index 128914c..f3eda11 100644
--- a/lua/gp/init.lua
+++ b/lua/gp/init.lua
@@ -1031,22 +1031,38 @@ M.chat_respond = function(params)
 	agent_suffix = M.render.template(agent_suffix, { ["{{agent}}"] = agent_name })
 
 	local old_default_user_prefix = "🗨:"
+	local in_cot_block = false -- Flag to track if we're inside a CoT block
+
 	for index = start_index, end_index do
 		local line = lines[index]
-		if line:sub(1, #M.config.chat_user_prefix) == M.config.chat_user_prefix then
-			table.insert(messages, { role = role, content = content })
-			role = "user"
-			content = line:sub(#M.config.chat_user_prefix + 1)
-		elseif line:sub(1, #old_default_user_prefix) == old_default_user_prefix then
-			table.insert(messages, { role = role, content = content })
-			role = "user"
-			content = line:sub(#old_default_user_prefix + 1)
-		elseif line:sub(1, #agent_prefix) == agent_prefix then
-			table.insert(messages, { role = role, content = content })
-			role = "assistant"
-			content = ""
-		elseif role ~= "" then
-			content = content .. "\n" .. line
+
+		-- Check if the line starts with <details> or </details>
+		if line:match("^%s*<details>") then
+			in_cot_block = true
+		end
+
+		-- Skip lines if we're inside a CoT block
+		if not in_cot_block then
+			-- Original logic for handling chat messages
+			if line:sub(1, #M.config.chat_user_prefix) == M.config.chat_user_prefix then
+				table.insert(messages, { role = role, content = content })
+				role = "user"
+				content = line:sub(#M.config.chat_user_prefix + 1)
+			elseif line:sub(1, #old_default_user_prefix) == old_default_user_prefix then
+				table.insert(messages, { role = role, content = content })
+				role = "user"
+				content = line:sub(#old_default_user_prefix + 1)
+			elseif line:sub(1, #agent_prefix) == agent_prefix then
+				table.insert(messages, { role = role, content = content })
+				role = "assistant"
+				content = ""
+			elseif role ~= "" then
+				content = content .. "\n" .. line
+			end
+		end
+
+		if line:match("^%s*</details>") then
+			in_cot_block = false
 		end
 	end
 	-- insert last message not handled in loop
@@ -1074,12 +1090,21 @@ M.chat_respond = function(params)
 	local last_content_line = M.helpers.last_content_line(buf)
 	vim.api.nvim_buf_set_lines(buf, last_content_line, last_content_line, false, { "", agent_prefix .. agent_suffix, "" })
 
+	local offset = 0
+	-- Add CoT for DeepSeek-Reasoner
+	if agent_name == "DeepSeekReasoner" then
+		vim.api.nvim_buf_set_lines(buf, last_content_line + 3, last_content_line + 3, false,
+			{ "<details>", "<summary>CoT</summary><!-- {{{ -->", "" })
+		offset = 1
+	end
+
 	-- call the model and write response
 	M.dispatcher.query(
 		buf,
 		headers.provider or agent.provider,
 		M.dispatcher.prepare_payload(messages, headers.model or agent.model, headers.provider or agent.provider),
-		M.dispatcher.create_handler(buf, win, M.helpers.last_content_line(buf), true, "", not M.config.chat_free_cursor),
+		M.dispatcher.create_handler(buf, win, M.helpers.last_content_line(buf) + offset, true, "",
+			not M.config.chat_free_cursor),
 		vim.schedule_wrap(function(qid)
 			local qt = M.tasker.get_query(qid)
 			if not qt then
@@ -1125,7 +1150,8 @@ M.chat_respond = function(params)
 					topic_handler,
 					vim.schedule_wrap(function()
 						-- get topic from invisible buffer
-						local topic = vim.api.nvim_buf_get_lines(topic_buf, 0, -1, false)[1]
+						-- instead of the first line, get the last two line can skip CoT
+						local topic = vim.api.nvim_buf_get_lines(topic_buf, -3, -1, false)[1]
 						-- close invisible buffer
 						vim.api.nvim_buf_delete(topic_buf, { force = true })
 						-- strip whitespace from ends of topic

From 4245e2019b9a7ba40a16987e39bb96b3bd7f5397 Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Mon, 27 Jan 2025 12:03:29 +0800
Subject: [PATCH 4/8] fix: disable check empty for `qt.response`

---
 lua/gp/dispatcher.lua | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index 19a2348..0936709 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -338,9 +338,9 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 				end
 
 
-				if qt.response == "" then
-					logger.error(qt.provider .. " response is empty: \n" .. vim.inspect(qt.raw_response))
-				end
+				-- if qt.response == "" then
+				-- 	logger.error(qt.provider .. " response is empty: \n" .. vim.inspect(qt.raw_response))
+				-- end
 
 				-- optional on_exit handler
 				if type(on_exit) == "function" then

From b5989e867a46747b4e88cb2e1268b3a6a0fd9d29 Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Mon, 27 Jan 2025 13:52:47 +0800
Subject: [PATCH 5/8] fix: complete details block on half exit for CoT

---
 lua/gp/dispatcher.lua | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index 0936709..ce8e166 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -337,6 +337,11 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 					end
 				end
 
+				if is_reasoning then
+					handler(qid, "\n", true)
+					handler(qid, "\n</details><!-- }}} -->\n\n", false)
+					is_reasoning = false
+				end
 
 				-- if qt.response == "" then
 				-- 	logger.error(qt.provider .. " response is empty: \n" .. vim.inspect(qt.raw_response))

From b61b10719e8305e901e33a26a18a413abfbbc80e Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Thu, 30 Jan 2025 15:17:57 +0800
Subject: [PATCH 6/8] fix: Delete fold marks. Use TS to fold (block_quote).

---
 lua/gp/dispatcher.lua | 4 ++--
 lua/gp/init.lua       | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index ce8e166..9e8c703 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -289,7 +289,7 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 				elseif content ~= "" and type(content) == "string" then
 					if is_reasoning then
 						handler(qid, "\n", true)
-						handler(qid, "\n</details><!-- }}} -->\n\n", false)
+						handler(qid, "\n</details>\n\n", false)
 						is_reasoning = false
 					end
 					qt.response = qt.response .. content
@@ -339,7 +339,7 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 
 				if is_reasoning then
 					handler(qid, "\n", true)
-					handler(qid, "\n</details><!-- }}} -->\n\n", false)
+					handler(qid, "\n</details>\n\n", false)
 					is_reasoning = false
 				end
 
diff --git a/lua/gp/init.lua b/lua/gp/init.lua
index f3eda11..32040b2 100644
--- a/lua/gp/init.lua
+++ b/lua/gp/init.lua
@@ -1094,7 +1094,7 @@ M.chat_respond = function(params)
 	-- Add CoT for DeepSeek-Reasoner
 	if agent_name == "DeepSeekReasoner" then
 		vim.api.nvim_buf_set_lines(buf, last_content_line + 3, last_content_line + 3, false,
-			{ "<details>", "<summary>CoT</summary><!-- {{{ -->", "" })
+			{ "<details>", "<summary>CoT</summary>", "" })
 		offset = 1
 	end
 

From 2b3e095ae525b95d30c23c1bfa4f6a5ffc6182ed Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Wed, 12 Feb 2025 11:11:31 +0800
Subject: [PATCH 7/8] fix: add "deepseek-r1" name detection after changing
 provider.

---
 lua/gp/dispatcher.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index 9e8c703..1e03c8d 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -211,7 +211,7 @@ end
 ---@param on_exit function | nil # optional on_exit handler
 ---@param callback function | nil # optional callback handler
 local query = function(buf, provider, payload, handler, on_exit, callback)
-	local is_reasoning = payload.model == "deepseek-reasoner"
+	local is_reasoning = payload.model == "deepseek-reasoner" or payload.model == "deepseek-r1"
 	-- make sure handler is a function
 	if type(handler) ~= "function" then
 		logger.error(

From dfb4cb999a9ca6aee3eccb9ccc84834921c58935 Mon Sep 17 00:00:00 2001
From: yuukibarns <yuukibarns@gmail.com>
Date: Sun, 16 Mar 2025 10:42:45 +0800
Subject: [PATCH 8/8] fix: wrap CoT with <think> tag

---
 lua/gp/dispatcher.lua | 31 +++++++++----------------------
 lua/gp/init.lua       | 26 +++++++++++++++++---------
 2 files changed, 26 insertions(+), 31 deletions(-)

diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua
index 1e03c8d..9e03102 100644
--- a/lua/gp/dispatcher.lua
+++ b/lua/gp/dispatcher.lua
@@ -187,19 +187,6 @@ D.prepare_payload = function(messages, model, provider)
 		output.stream = false
 	end
 
-	if provider == "deepseek" and model.model == "deepseek-reasoner" then
-		for i = #messages, 1, -1 do
-			if messages[i].role == "system" then
-				table.remove(messages, i)
-			end
-		end
-		-- remove max_tokens, top_p, temperature for reason model
-		output.max_tokens = nil
-		output.temperature = nil
-		output.top_p = nil
-		output.stream = true
-	end
-
 	return output
 end
 
@@ -210,8 +197,8 @@ end
 ---@param handler function # response handler
 ---@param on_exit function | nil # optional on_exit handler
 ---@param callback function | nil # optional callback handler
-local query = function(buf, provider, payload, handler, on_exit, callback)
-	local is_reasoning = payload.model == "deepseek-reasoner" or payload.model == "deepseek-r1"
+---@param is_reasoning boolean # whether model is reasoning model
+local query = function(buf, provider, payload, handler, on_exit, callback, is_reasoning)
 	-- make sure handler is a function
 	if type(handler) ~= "function" then
 		logger.error(
@@ -288,8 +275,7 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 					handler(qid, reasoning_content, true)
 				elseif content ~= "" and type(content) == "string" then
 					if is_reasoning then
-						handler(qid, "\n", true)
-						handler(qid, "\n</details>\n\n", false)
+						handler(qid, "\n</details>\n</think>\n", false)
 						is_reasoning = false
 					end
 					qt.response = qt.response .. content
@@ -338,8 +324,8 @@ local query = function(buf, provider, payload, handler, on_exit, callback)
 				end
 
 				if is_reasoning then
-					handler(qid, "\n", true)
-					handler(qid, "\n</details>\n\n", false)
+					handler(qid, "\n", false)
+					handler(qid, "\n</details>\n</think>\n", false)
 					is_reasoning = false
 				end
 
@@ -456,16 +442,17 @@ end
 ---@param handler function # response handler
 ---@param on_exit function | nil # optional on_exit handler
 ---@param callback function | nil # optional callback handler
-D.query = function(buf, provider, payload, handler, on_exit, callback)
+---@param is_reasoning boolean # whether the model is reasoning model
+D.query = function(buf, provider, payload, handler, on_exit, callback, is_reasoning)
 	if provider == "copilot" then
 		return vault.run_with_secret(provider, function()
 			vault.refresh_copilot_bearer(function()
-				query(buf, provider, payload, handler, on_exit, callback)
+				query(buf, provider, payload, handler, on_exit, callback, is_reasoning)
 			end)
 		end)
 	end
 	vault.run_with_secret(provider, function()
-		query(buf, provider, payload, handler, on_exit, callback)
+		query(buf, provider, payload, handler, on_exit, callback, is_reasoning)
 	end)
 end
 
diff --git a/lua/gp/init.lua b/lua/gp/init.lua
index 32040b2..5aa1420 100644
--- a/lua/gp/init.lua
+++ b/lua/gp/init.lua
@@ -1036,8 +1036,7 @@ M.chat_respond = function(params)
 	for index = start_index, end_index do
 		local line = lines[index]
 
-		-- Check if the line starts with <details> or </details>
-		if line:match("^%s*<details>") then
+		if line:match("^<think>$") then
 			in_cot_block = true
 		end
 
@@ -1061,7 +1060,7 @@ M.chat_respond = function(params)
 			end
 		end
 
-		if line:match("^%s*</details>") then
+		if line:match("^</think>$") then
 			in_cot_block = false
 		end
 	end
@@ -1079,6 +1078,8 @@ M.chat_respond = function(params)
 		-- make it multiline again if it contains escaped newlines
 		content = content:gsub("\\n", "\n")
 		messages[1] = { role = "system", content = content }
+	else
+		table.remove(messages, 1)
 	end
 
 	-- strip whitespace from ends of content
@@ -1091,11 +1092,13 @@ M.chat_respond = function(params)
 	vim.api.nvim_buf_set_lines(buf, last_content_line, last_content_line, false, { "", agent_prefix .. agent_suffix, "" })
 
 	local offset = 0
-	-- Add CoT for DeepSeek-Reasoner
-	if agent_name == "DeepSeekReasoner" then
+	local is_reasoning = false
+	-- Add CoT for DeepSeekReasoner
+	if string.match(agent_name, "^DeepSeekReasoner") then
 		vim.api.nvim_buf_set_lines(buf, last_content_line + 3, last_content_line + 3, false,
-			{ "<details>", "<summary>CoT</summary>", "" })
+			{ "<think>", "<details>", "<summary>CoT</summary>", "" })
 		offset = 1
+		is_reasoning = true
 	end
 
 	-- call the model and write response
@@ -1167,7 +1170,9 @@ M.chat_respond = function(params)
 						-- replace topic in current buffer
 						M.helpers.undojoin(buf)
 						vim.api.nvim_buf_set_lines(buf, 0, 1, false, { "# topic: " .. topic })
-					end)
+					end),
+					nil,
+					false
 				)
 			end
 			if not M.config.chat_free_cursor then
@@ -1175,7 +1180,9 @@ M.chat_respond = function(params)
 				M.helpers.cursor_to_line(line, buf, win)
 			end
 			vim.cmd("doautocmd User GpDone")
-		end)
+		end),
+		nil,
+		is_reasoning
 	)
 end
 
@@ -1961,7 +1968,8 @@ M.Prompt = function(params, target, agent, template, prompt, whisper, callback)
 				on_exit(qid)
 				vim.cmd("doautocmd User GpDone")
 			end),
-			callback
+			callback,
+			false
 		)
 	end