diff --git a/lua/gp/config.lua b/lua/gp/config.lua index 1924e43..cdbe52b 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, diff --git a/lua/gp/dispatcher.lua b/lua/gp/dispatcher.lua index d4214aa..44c37ce 100644 --- a/lua/gp/dispatcher.lua +++ b/lua/gp/dispatcher.lua @@ -200,7 +200,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) +---@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( @@ -241,9 +242,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 @@ -267,10 +274,15 @@ 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\n\n", false) + is_reasoning = false + end qt.response = qt.response .. content - handler(qid, content) + handler(qid, content, false) end end end @@ -314,11 +326,16 @@ local query = function(buf, provider, payload, handler, on_exit, callback) end end - - if qt.response == "" then - logger.error(qt.provider .. " response is empty: \n" .. vim.inspect(qt.raw_response)) + if is_reasoning then + handler(qid, "\n", false) + handler(qid, "\n\n\n", false) + is_reasoning = false 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 on_exit(qid) @@ -396,7 +413,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 {}) @@ -428,16 +445,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 @@ -466,7 +484,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 @@ -506,6 +524,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]) @@ -514,9 +539,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") diff --git a/lua/gp/init.lua b/lua/gp/init.lua index 128914c..fe2253d 100644 --- a/lua/gp/init.lua +++ b/lua/gp/init.lua @@ -1031,22 +1031,37 @@ 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 + + if line:match("^$") 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("^$") then + in_cot_block = false end end -- insert last message not handled in loop @@ -1063,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 @@ -1074,12 +1091,23 @@ 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 + 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, + { "", "
", "CoT", "" }) + offset = 1 + is_reasoning = true + 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 +1153,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 @@ -1133,15 +1162,17 @@ M.chat_respond = function(params) -- strip dot from end of topic topic = topic:gsub("%.$", "") - -- if topic is empty do not replace it - if topic == "" then + -- if topic is empty or too long do not replace it + if topic == "" or #topic > 50 then return end -- 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 @@ -1149,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 @@ -1935,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