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