Skip to content

Commit 0593155

Browse files
committed
Add handling for no response from stream and improve curl performance
Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
1 parent e3d7b4b commit 0593155

2 files changed

Lines changed: 97 additions & 20 deletions

File tree

lua/CopilotChat/copilot.lua

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,30 @@ local version_headers = {
4848
.. vim.version().patch,
4949
['editor-plugin-version'] = 'CopilotChat.nvim/2.0.0',
5050
['user-agent'] = 'CopilotChat.nvim/2.0.0',
51+
['sec-fetch-site'] = 'none',
52+
['sec-fetch-mode'] = 'no-cors',
53+
['sec-fetch-dest'] = 'empty',
54+
['priority'] = 'u=4, i',
55+
-- ['x-github-api-version'] = '2023-07-07',
5156
}
5257

5358
local curl_get = async.wrap(function(url, opts, callback)
54-
opts = vim.tbl_deep_extend('force', opts, { callback = callback })
59+
opts = vim.tbl_deep_extend('force', opts, {
60+
callback = callback,
61+
on_error = function(err)
62+
callback(nil, vim.inspect(err))
63+
end,
64+
})
5565
curl.get(url, opts)
5666
end, 3)
5767

5868
local curl_post = async.wrap(function(url, opts, callback)
59-
opts = vim.tbl_deep_extend('force', opts, { callback = callback })
69+
opts = vim.tbl_deep_extend('force', opts, {
70+
callback = callback,
71+
on_error = function(err)
72+
callback(nil, vim.inspect(err))
73+
end,
74+
})
6075
curl.post(url, opts)
6176
end, 3)
6277

@@ -79,7 +94,8 @@ local function machine_id()
7994
local hex_chars = '0123456789abcdef'
8095
local hex = ''
8196
for _ = 1, length do
82-
hex = hex .. hex_chars:sub(math.random(1, #hex_chars), math.random(1, #hex_chars))
97+
local index = math.random(1, #hex_chars)
98+
hex = hex .. hex_chars:sub(index, index)
8399
end
84100
return hex
85101
end
@@ -235,7 +251,6 @@ local function generate_ask_request(
235251

236252
if stream then
237253
return {
238-
intent = true,
239254
model = model,
240255
n = 1,
241256
stream = true,
@@ -293,6 +308,26 @@ local Copilot = class(function(self, proxy, allow_insecure)
293308
self.models = nil
294309
self.claude_enabled = false
295310
self.current_job = nil
311+
self.extra_args = {
312+
-- Retry failed requests twice
313+
'--retry',
314+
'2',
315+
-- Wait 1 second between retries
316+
'--retry-delay',
317+
'1',
318+
-- Maximum time for the request
319+
'--max-time',
320+
math.floor(timeout * 2 / 1000),
321+
-- Timeout for initial connection
322+
'--connect-timeout',
323+
'10',
324+
'--no-keepalive', -- Don't reuse connections
325+
'--tcp-nodelay', -- Disable Nagle's algorithm for faster streaming
326+
'--no-buffer', -- Disable output buffering for streaming
327+
'--fail', -- Return error on HTTP errors (4xx, 5xx)
328+
'--silent', -- Don't show progress meter
329+
'--show-error', -- Show errors even when silent
330+
}
296331
end)
297332

298333
function Copilot:authenticate()
@@ -314,13 +349,18 @@ function Copilot:authenticate()
314349
headers[key] = value
315350
end
316351

317-
local response = curl_get('https://api.github.com/copilot_internal/v2/token', {
352+
local response, err = curl_get('https://api.github.com/copilot_internal/v2/token', {
318353
timeout = timeout,
319354
headers = headers,
320355
proxy = self.proxy,
321356
insecure = self.allow_insecure,
357+
raw = self.extra_args,
322358
})
323359

360+
if err then
361+
error(err)
362+
end
363+
324364
if response.status ~= 200 then
325365
error('Failed to authenticate: ' .. tostring(response.status))
326366
end
@@ -351,13 +391,18 @@ function Copilot:fetch_models()
351391
return self.models
352392
end
353393

354-
local response = curl_get('https://api.githubcopilot.com/models', {
394+
local response, err = curl_get('https://api.githubcopilot.com/models', {
355395
timeout = timeout,
356396
headers = self:authenticate(),
357397
proxy = self.proxy,
358398
insecure = self.allow_insecure,
399+
raw = self.extra_args,
359400
})
360401

402+
if err then
403+
error(err)
404+
end
405+
361406
if response.status ~= 200 then
362407
error('Failed to fetch models: ' .. tostring(response.status))
363408
end
@@ -385,14 +430,19 @@ function Copilot:enable_claude()
385430
local business_msg =
386431
'Claude is probably enabled (for business users needs to be enabled manually).'
387432

388-
local response = curl_post('https://api.githubcopilot.com/models/claude-3.5-sonnet/policy', {
433+
local response, err = curl_post('https://api.githubcopilot.com/models/claude-3.5-sonnet/policy', {
389434
timeout = timeout,
390435
headers = self:authenticate(),
391436
proxy = self.proxy,
392437
insecure = self.allow_insecure,
393438
body = temp_file('{"state": "enabled"}'),
439+
raw = self.extra_args,
394440
})
395441

442+
if err then
443+
error(err)
444+
end
445+
396446
-- Handle business user case
397447
if response.status ~= 200 and string.find(tostring(response.body), business_check) then
398448
self.claude_enabled = true
@@ -494,29 +544,44 @@ function Copilot:ask(prompt, opts)
494544

495545
local last_message = nil
496546
local errored = false
547+
local finished = false
497548
local full_response = ''
498549

499-
local function handle_error(err)
500-
errored = true
501-
full_response = err
550+
local function finish_stream(err, job)
551+
if err then
552+
errored = true
553+
full_response = err
554+
end
555+
556+
finished = true
557+
vim.schedule(function()
558+
job:shutdown()
559+
end)
502560
end
503561

504-
local function stream_func(err, line)
505-
if not line or errored then
562+
local function stream_func(err, line, job)
563+
if not line or errored or finished then
506564
return
507565
end
508566

509567
if self.current_job ~= job_id then
568+
finish_stream(nil, job)
510569
return
511570
end
512571

513572
if err or vim.startswith(line, '{"error"') then
514-
handle_error('Failed to get response: ' .. (err and vim.inspect(err) or line))
573+
finish_stream('Failed to get response: ' .. (err and vim.inspect(err) or line), job)
574+
return
575+
end
576+
577+
if not vim.startswith(line, 'data: ') then
515578
return
516579
end
517580

518-
line = line:gsub('^%s*data: ', '')
519-
if line == '' or line == '[DONE]' then
581+
line = line:gsub('^%s*data:%s*', ''):gsub('%s*$', '')
582+
583+
if line == '[DONE]' then
584+
finish_stream(nil, job)
520585
return
521586
end
522587

@@ -528,7 +593,7 @@ function Copilot:ask(prompt, opts)
528593
})
529594

530595
if not ok then
531-
handle_error('Failed to parse response: ' .. vim.inspect(content) .. '\n' .. line)
596+
finish_stream('Failed to parse response: ' .. vim.inspect(content) .. '\n' .. line, job)
532597
return
533598
end
534599

@@ -538,8 +603,7 @@ function Copilot:ask(prompt, opts)
538603

539604
last_message = content
540605
local choice = content.choices[1]
541-
local is_full = choice.message ~= nil
542-
content = is_full and choice.message.content or choice.delta.content
606+
content = choice.message and choice.message.content or choice.delta and choice.delta.content
543607

544608
if not content then
545609
return
@@ -569,13 +633,14 @@ function Copilot:ask(prompt, opts)
569633
self:enable_claude()
570634
end
571635

572-
local response = curl_post('https://api.githubcopilot.com/chat/completions', {
636+
local response, err = curl_post('https://api.githubcopilot.com/chat/completions', {
573637
timeout = timeout,
574638
headers = self:authenticate(),
575639
body = temp_file(body),
576640
proxy = self.proxy,
577641
insecure = self.allow_insecure,
578642
stream = stream_func,
643+
raw = self.extra_args,
579644
})
580645

581646
if self.current_job ~= job_id then
@@ -584,6 +649,11 @@ function Copilot:ask(prompt, opts)
584649

585650
self.current_job = nil
586651

652+
if err then
653+
error(err)
654+
return
655+
end
656+
587657
if not response then
588658
error('Failed to get response')
589659
return
@@ -662,14 +732,20 @@ function Copilot:embed(inputs, opts)
662732
local out = {}
663733

664734
for _, chunk in ipairs(chunks) do
665-
local response = curl_post('https://api.githubcopilot.com/embeddings', {
735+
local response, err = curl_post('https://api.githubcopilot.com/embeddings', {
666736
timeout = timeout,
667737
headers = self:authenticate(),
668738
body = temp_file(vim.json.encode(generate_embedding_request(chunk, model))),
669739
proxy = self.proxy,
670740
insecure = self.allow_insecure,
741+
raw = self.extra_args,
671742
})
672743

744+
if err then
745+
error(err)
746+
return
747+
end
748+
673749
if not response then
674750
error('Failed to get response')
675751
return

lua/CopilotChat/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ function M.ask(prompt, config, source)
396396
end
397397

398398
local function on_error(err)
399+
log.error(err)
399400
vim.schedule(function()
400401
append('\n\n' .. config.error_header .. config.separator .. '\n\n', config)
401402
append('```\n' .. get_error_message(err) .. '\n```', config)

0 commit comments

Comments
 (0)