diff --git a/local/ai-provider/lua/ai-provider/curl.lua b/local/ai-provider/lua/ai-provider/curl.lua index 7d6f151..1574aa8 100644 --- a/local/ai-provider/lua/ai-provider/curl.lua +++ b/local/ai-provider/lua/ai-provider/curl.lua @@ -138,6 +138,39 @@ function M.stream_json_lines(request) local stderr = {} local stdout_lines = {} local http_status = nil + local stdout_buffer = '' + + local function process_stdout_line(line) + line = line:gsub('^%s+', ''):gsub('%s+$', '') + if line == '' then + return + end + + local status = line:match('^' .. status_marker .. '(%d+)$') + if status then + http_status = tonumber(status) + return + end + + local payload = line + local event_data = line:match '^data:%s*(.*)$' + if event_data then + if event_data == '[DONE]' then + return + end + payload = event_data + end + + if #stdout_lines < 20 then + table.insert(stdout_lines, payload) + end + + local ok, data = pcall(vim.json.decode, payload) + if ok and type(data) == 'table' and request.on_json_line then + schedule(request.on_json_line, data, line) + end + end + local job = Job:new { command = 'curl', args = args, @@ -146,33 +179,21 @@ function M.stream_json_lines(request) return end - for line in tostring(output):gmatch('[^\r\n]+') do - line = line:gsub('^%s+', ''):gsub('%s+$', '') - if line ~= '' then - local status = line:match('^' .. status_marker .. '(%d+)$') - if status then - http_status = tonumber(status) - else - local payload = line - local event_data = line:match '^data:%s*(.*)$' - if event_data then - if event_data == '[DONE]' then - goto continue - end - payload = event_data - end - - if #stdout_lines < 20 then - table.insert(stdout_lines, payload) - end - - local ok, data = pcall(vim.json.decode, payload) - if ok and type(data) == 'table' and request.on_json_line then - schedule(request.on_json_line, data, line) - end - end + local text = tostring(output) + if stdout_buffer == '' and not text:find('[\r\n]') then + process_stdout_line(text) + return + end + + stdout_buffer = stdout_buffer .. text + while true do + local newline_at = stdout_buffer:find('[\r\n]') + if not newline_at then + break end - ::continue:: + + process_stdout_line(stdout_buffer:sub(1, newline_at - 1)) + stdout_buffer = stdout_buffer:sub(newline_at + 1) end end, on_stderr = function(_, line) @@ -181,6 +202,11 @@ function M.stream_json_lines(request) end end, on_exit = function(_, code) + if stdout_buffer ~= '' and not (request.is_cancelled and request.is_cancelled()) then + process_stdout_line(stdout_buffer) + stdout_buffer = '' + end + local error_parts = {} if #stderr > 0 then table.insert(error_parts, table.concat(stderr, '\n')) diff --git a/local/ai-provider/lua/ai-provider/providers/ollama.lua b/local/ai-provider/lua/ai-provider/providers/ollama.lua index 88d623d..5487bcb 100644 --- a/local/ai-provider/lua/ai-provider/providers/ollama.lua +++ b/local/ai-provider/lua/ai-provider/providers/ollama.lua @@ -6,7 +6,6 @@ local DEFAULT_ENDPOINT = 'http://127.0.0.1:11434' local HEALTH_CACHE_TTL = 60 local MODEL_CACHE_TTL = 60 local STATUS_THROTTLE_MS = 100 -local MAX_PENDING_CHUNK_CHARS = 4000 local state = { health = nil, @@ -190,7 +189,6 @@ function M.chat(request) local pending_statuses = {} local pending_status_flush_scheduled = false local pending_chunks = {} - local pending_chunk_chars = 0 local pending_chunk_flush_scheduled = false local function flush_pending_statuses() @@ -211,13 +209,11 @@ function M.chat(request) pending_chunk_flush_scheduled = false if not request.on_chunk or is_cancelled(request) then pending_chunks = {} - pending_chunk_chars = 0 return end local chunks_to_flush = pending_chunks pending_chunks = {} - pending_chunk_chars = 0 for _, item in ipairs(chunks_to_flush) do request.on_chunk(item.chunk, item.data, item.kind) end @@ -244,13 +240,12 @@ function M.chat(request) return end - local item = { chunk = chunk, data = data, kind = kind } - table.insert(pending_chunks, item) - pending_chunk_chars = pending_chunk_chars + #chunk - - while pending_chunk_chars > MAX_PENDING_CHUNK_CHARS and #pending_chunks > 1 do - local removed = table.remove(pending_chunks, 1) - pending_chunk_chars = pending_chunk_chars - #(removed.chunk or '') + local previous = pending_chunks[#pending_chunks] + if previous and previous.kind == kind then + previous.chunk = previous.chunk .. chunk + previous.data = data + else + table.insert(pending_chunks, { chunk = chunk, data = data, kind = kind }) end schedule_chunk_flush() end diff --git a/lua/plugins/ai-provider.lua b/lua/plugins/ai-provider.lua index b97484e..d8b94a6 100644 --- a/lua/plugins/ai-provider.lua +++ b/lua/plugins/ai-provider.lua @@ -12,7 +12,7 @@ return { default_model = 'auto', }, ollama = { - default_model = 'gemma4:e4b', + default_model = 'gemma4:e2b', context_size = 1024 * 8, timeout = 180000, keep_alive = '1h',