@@ -196,6 +196,36 @@ local function get_github_models_token(tag)
196196 return github_device_flow (tag , ' 178c6fc778ccc68e1d6a' , ' read:user copilot' )
197197end
198198
199+ --- Helper function to extract text content from Responses API output parts
200+ --- @param parts table Array of content parts from Responses API
201+ --- @return string The concatenated text content
202+ local function extract_text_from_parts (parts )
203+ local content = ' '
204+ if not parts or type (parts ) ~= ' table' then
205+ return content
206+ end
207+
208+ for _ , part in ipairs (parts ) do
209+ if type (part ) == ' table' then
210+ -- Handle different content types from Responses API
211+ if part .type == ' output_text' or part .type == ' text' then
212+ content = content .. (part .text or ' ' )
213+ elseif part .output_text then
214+ -- Handle nested output_text
215+ if type (part .output_text ) == ' string' then
216+ content = content .. part .output_text
217+ elseif type (part .output_text ) == ' table' and part .output_text .text then
218+ content = content .. part .output_text .text
219+ end
220+ end
221+ elseif type (part ) == ' string' then
222+ content = content .. part
223+ end
224+ end
225+
226+ return content
227+ end
228+
199229--- @class CopilotChat.config.providers.Options
200230--- @field model CopilotChat.client.Model
201231--- @field temperature number ?
@@ -308,6 +338,10 @@ M.copilot = {
308338 return model .capabilities .type == ' chat' and model .model_picker_enabled
309339 end )
310340 :map (function (model )
341+ local supported_endpoints = model .supported_endpoints or {}
342+ -- Pre-compute whether this model uses the Responses API
343+ local use_responses = vim .tbl_contains (supported_endpoints , ' /responses' )
344+
311345 return {
312346 id = model .id ,
313347 name = model .name ,
@@ -318,6 +352,7 @@ M.copilot = {
318352 tools = model .capabilities .supports .tool_calls ,
319353 policy = not model [' policy' ] or model [' policy' ][' state' ] == ' enabled' ,
320354 version = model .version ,
355+ use_responses = use_responses ,
321356 }
322357 end )
323358 :totable ()
@@ -347,6 +382,63 @@ M.copilot = {
347382 prepare_input = function (inputs , opts )
348383 local is_o1 = vim .startswith (opts .model .id , ' o1' )
349384
385+ -- Check if this model uses the Responses API
386+ if opts .model .use_responses then
387+ -- Prepare input for Responses API
388+ local instructions = nil
389+ local input_messages = {}
390+
391+ for _ , msg in ipairs (inputs ) do
392+ if msg .role == constants .ROLE .SYSTEM then
393+ -- Combine system messages as instructions
394+ if instructions then
395+ instructions = instructions .. ' \n\n ' .. msg .content
396+ else
397+ instructions = msg .content
398+ end
399+ else
400+ -- Include the message in the input array
401+ table.insert (input_messages , {
402+ role = msg .role ,
403+ content = msg .content ,
404+ })
405+ end
406+ end
407+
408+ -- The Responses API expects the input field to be an array of message objects
409+ local out = {
410+ model = opts .model .id ,
411+ -- Always request streaming for Responses API (honor model.streaming or default to true)
412+ stream = opts .model .streaming ~= false ,
413+ input = input_messages ,
414+ }
415+
416+ -- Add instructions if we have any system messages
417+ if instructions then
418+ out .instructions = instructions
419+ end
420+
421+ -- Add tools for Responses API if available
422+ if opts .tools and opts .model .tools then
423+ out .tools = vim .tbl_map (function (tool )
424+ return {
425+ type = ' function' ,
426+ [' function' ] = {
427+ name = tool .name ,
428+ description = tool .description ,
429+ parameters = tool .schema ,
430+ strict = true ,
431+ },
432+ }
433+ end , opts .tools )
434+ end
435+
436+ -- Note: temperature is not supported by Responses API, so we don't include it
437+
438+ return out
439+ end
440+
441+ -- Original Chat Completion API logic
350442 inputs = vim .tbl_map (function (input )
351443 local output = {
352444 role = input .role ,
@@ -411,7 +503,179 @@ M.copilot = {
411503 return out
412504 end ,
413505
414- prepare_output = function (output )
506+ prepare_output = function (output , opts )
507+ -- Check if this model uses the Responses API
508+ if opts and opts .model and opts .model .use_responses then
509+ -- Handle Responses API output format
510+ local content = ' '
511+ local reasoning = ' '
512+ local finish_reason = nil
513+ local total_tokens = 0
514+ local tool_calls = {}
515+
516+ -- Check for error in response
517+ if output .error then
518+ -- Surface the error as a finish reason to stop processing
519+ local error_msg = output .error
520+ if type (error_msg ) == ' table' then
521+ error_msg = error_msg .message or vim .inspect (error_msg )
522+ end
523+ return {
524+ content = ' ' ,
525+ reasoning = ' ' ,
526+ finish_reason = ' error: ' .. tostring (error_msg ),
527+ total_tokens = nil ,
528+ tool_calls = {},
529+ }
530+ end
531+
532+ if output .type then
533+ -- This is a streaming response from Responses API
534+ if output .type == ' response.created' or output .type == ' response.in_progress' then
535+ -- In-progress events, we don't have content yet
536+ return {
537+ content = ' ' ,
538+ reasoning = ' ' ,
539+ finish_reason = nil ,
540+ total_tokens = nil ,
541+ tool_calls = {},
542+ }
543+ elseif output .type == ' response.completed' then
544+ -- Completed response: do NOT resend content here to avoid duplication.
545+ -- Only signal finish and capture usage/reasoning.
546+ local response = output .response
547+ if response then
548+ if response .reasoning and response .reasoning .summary then
549+ reasoning = response .reasoning .summary
550+ end
551+ if response .usage then
552+ total_tokens = response .usage .total_tokens
553+ end
554+ finish_reason = ' stop'
555+ end
556+ return {
557+ content = ' ' ,
558+ reasoning = reasoning ,
559+ finish_reason = finish_reason ,
560+ total_tokens = total_tokens ,
561+ tool_calls = {},
562+ }
563+ elseif output .type == ' response.content.delta' or output .type == ' response.output_text.delta' then
564+ -- Streaming content delta
565+ if output .delta then
566+ if type (output .delta ) == ' string' then
567+ content = output .delta
568+ elseif type (output .delta ) == ' table' then
569+ if output .delta .content then
570+ content = output .delta .content
571+ elseif output .delta .output_text then
572+ content = extract_text_from_parts ({ output .delta .output_text })
573+ elseif output .delta .text then
574+ content = output .delta .text
575+ end
576+ end
577+ end
578+ elseif output .type == ' response.delta' then
579+ -- Handle response.delta with nested output_text
580+ if output .delta and output .delta .output_text then
581+ content = extract_text_from_parts ({ output .delta .output_text })
582+ end
583+ elseif output .type == ' response.content.done' or output .type == ' response.output_text.done' then
584+ -- Terminal content event; keep streaming open until response.completed provides usage info
585+ finish_reason = nil
586+ elseif output .type == ' response.error' then
587+ -- Handle error event
588+ local error_msg = output .error
589+ if type (error_msg ) == ' table' then
590+ error_msg = error_msg .message or vim .inspect (error_msg )
591+ end
592+ finish_reason = ' error: ' .. tostring (error_msg )
593+ elseif output .type == ' response.tool_call.delta' then
594+ -- Handle tool call delta events
595+ if output .delta and output .delta .tool_calls then
596+ for _ , tool_call in ipairs (output .delta .tool_calls ) do
597+ local id = tool_call .id or (' tooluse_' .. (tool_call .index or 1 ))
598+ local existing_call = nil
599+ for _ , tc in ipairs (tool_calls ) do
600+ if tc .id == id then
601+ existing_call = tc
602+ break
603+ end
604+ end
605+ if not existing_call then
606+ table.insert (tool_calls , {
607+ id = id ,
608+ index = tool_call .index or # tool_calls + 1 ,
609+ name = tool_call .name or ' ' ,
610+ arguments = tool_call .arguments or ' ' ,
611+ })
612+ else
613+ -- Append arguments
614+ existing_call .arguments = existing_call .arguments .. (tool_call .arguments or ' ' )
615+ end
616+ end
617+ end
618+ end
619+ elseif output .response then
620+ -- Non-streaming response or final response
621+ local response = output .response
622+
623+ -- Check for error in the response object
624+ if response .error then
625+ local error_msg = response .error
626+ if type (error_msg ) == ' table' then
627+ error_msg = error_msg .message or vim .inspect (error_msg )
628+ end
629+ return {
630+ content = ' ' ,
631+ reasoning = ' ' ,
632+ finish_reason = ' error: ' .. tostring (error_msg ),
633+ total_tokens = nil ,
634+ tool_calls = {},
635+ }
636+ end
637+
638+ if response .output and # response .output > 0 then
639+ for _ , msg in ipairs (response .output ) do
640+ if msg .content and # msg .content > 0 then
641+ content = content .. extract_text_from_parts (msg .content )
642+ end
643+ -- Extract tool calls from output messages
644+ if msg .tool_calls then
645+ for i , tool_call in ipairs (msg .tool_calls ) do
646+ local id = tool_call .id or (' tooluse_' .. i )
647+ table.insert (tool_calls , {
648+ id = id ,
649+ index = tool_call .index or i ,
650+ name = tool_call .name or ' ' ,
651+ arguments = tool_call .arguments or ' ' ,
652+ })
653+ end
654+ end
655+ end
656+ end
657+
658+ if response .reasoning and response .reasoning .summary then
659+ reasoning = response .reasoning .summary
660+ end
661+
662+ if response .usage then
663+ total_tokens = response .usage .total_tokens
664+ end
665+
666+ finish_reason = response .status == ' completed' and ' stop' or nil
667+ end
668+
669+ return {
670+ content = content ,
671+ reasoning = reasoning ,
672+ finish_reason = finish_reason ,
673+ total_tokens = total_tokens ,
674+ tool_calls = tool_calls ,
675+ }
676+ end
677+
678+ -- Original Chat Completion API logic
415679 local tool_calls = {}
416680
417681 local choice
@@ -458,7 +722,13 @@ M.copilot = {
458722 }
459723 end ,
460724
461- get_url = function ()
725+ get_url = function (opts )
726+ -- Check if this model uses the Responses API
727+ if opts and opts .model and opts .model .use_responses then
728+ return ' https://api.githubcopilot.com/responses'
729+ end
730+
731+ -- Default to Chat Completion API
462732 return ' https://api.githubcopilot.com/chat/completions'
463733 end ,
464734}
0 commit comments