@@ -308,6 +308,10 @@ M.copilot = {
308308 return model .capabilities .type == ' chat' and model .model_picker_enabled
309309 end )
310310 :map (function (model )
311+ local supported_endpoints = model .supported_endpoints
312+ local responses_only = supported_endpoints
313+ and # supported_endpoints == 1
314+ and supported_endpoints [1 ] == ' /responses'
311315 return {
312316 id = model .id ,
313317 name = model .name ,
@@ -318,6 +322,8 @@ M.copilot = {
318322 tools = model .capabilities .supports .tool_calls ,
319323 policy = not model [' policy' ] or model [' policy' ][' state' ] == ' enabled' ,
320324 version = model .version ,
325+ supported_endpoints = supported_endpoints ,
326+ responses_only = responses_only ,
321327 }
322328 end )
323329 :totable ()
@@ -347,6 +353,66 @@ M.copilot = {
347353 prepare_input = function (inputs , opts )
348354 local is_o1 = vim .startswith (opts .model .id , ' o1' )
349355
356+ -- Handle OpenAI /responses style models
357+ if opts .model .responses_only then
358+ local system_prompt = nil
359+ for _ , m in ipairs (inputs ) do
360+ if m .role == constants .ROLE .SYSTEM and not utils .empty (m .content ) then
361+ system_prompt = m .content
362+ break
363+ end
364+ end
365+
366+ local function is_resource_msg (m )
367+ if m .role ~= constants .ROLE .USER or not m .content then
368+ return false
369+ end
370+ return vim .startswith (m .content , ' # ' ) or m .content :find (' ```' , 1 , true ) ~= nil
371+ end
372+
373+ -- Collect context resources
374+ local context_blocks = {}
375+ for _ , m in ipairs (inputs ) do
376+ if is_resource_msg (m ) then
377+ table.insert (context_blocks , m .content )
378+ end
379+ end
380+
381+ -- Build a single-turn input from conversation history (excluding resource blocks)
382+ local lines = {}
383+ if # context_blocks > 0 then
384+ table.insert (lines , ' # Context:' )
385+ table.insert (lines , table.concat (context_blocks , ' \n\n ' ))
386+ table.insert (lines , ' ' )
387+ end
388+
389+ local i = 1
390+ while i <= # inputs do
391+ local msg = inputs [i ]
392+ if msg .role == constants .ROLE .USER and not is_resource_msg (msg ) then
393+ local next_msg = inputs [i + 1 ]
394+ if next_msg and next_msg .role == constants .ROLE .ASSISTANT then
395+ table.insert (lines , ' ## I asked :\n ' .. (msg .content or ' ' ) .. ' \n ' )
396+ table.insert (lines , ' ## The answer was :\n ' .. (next_msg .content or ' ' ) .. ' \n ' )
397+ i = i + 2
398+ else
399+ -- Last user question
400+ table.insert (lines , ' # I ask:\n ' .. (msg .content or ' ' ) .. ' \n ' )
401+ i = i + 1
402+ end
403+ else
404+ i = i + 1
405+ end
406+ end
407+
408+ return {
409+ model = opts .model .id ,
410+ stream = opts .model .streaming or false ,
411+ instructions = system_prompt ,
412+ input = table.concat (lines , ' \n ' ),
413+ }
414+ end
415+
350416 inputs = vim .tbl_map (function (input )
351417 local output = {
352418 role = input .role ,
@@ -414,6 +480,52 @@ M.copilot = {
414480 prepare_output = function (output )
415481 local tool_calls = {}
416482
483+ -- Handle OpenAI /responses streaming and non-streaming
484+ if type (output ) == ' table' and output .type and vim .startswith (output .type , ' response.' ) then
485+ local t = output .type
486+ local content = nil
487+ local reasoning = nil
488+ local finish_reason = nil
489+ local usage = nil
490+
491+ if t :find (' response.output_text.delta' , 1 , true ) then
492+ local delta = output .delta or (output .data and output .data .delta ) or output .text
493+ if type (delta ) == ' table' then
494+ delta = delta .text or delta .content or delta [1 ]
495+ end
496+ content = delta
497+ elseif t == ' response.completed' then
498+ local resp = output .response or {}
499+ -- accumulate all output_text segments
500+ if resp .output then
501+ local parts = {}
502+ for _ , msg in ipairs (resp .output ) do
503+ if msg .content then
504+ for _ , c in ipairs (msg .content ) do
505+ if c .type == ' output_text' and c .text then
506+ table.insert (parts , c .text )
507+ end
508+ end
509+ end
510+ end
511+ content = table.concat (parts , ' ' )
512+ end
513+ usage = resp .usage and resp .usage .total_tokens or output .usage and output .usage .total_tokens
514+ finish_reason = ' stop'
515+ elseif t == ' response.error' then
516+ finish_reason = ' error'
517+ end
518+
519+ return {
520+ content = content ,
521+ reasoning = reasoning ,
522+ finish_reason = finish_reason ,
523+ total_tokens = usage ,
524+ tool_calls = tool_calls ,
525+ }
526+ end
527+
528+ -- Fallback to Chat Completions style
417529 local choice
418530 if output .choices and # output .choices > 0 then
419531 for _ , choice in ipairs (output .choices ) do
@@ -458,7 +570,10 @@ M.copilot = {
458570 }
459571 end ,
460572
461- get_url = function ()
573+ get_url = function (opts )
574+ if opts and opts .model and opts .model .responses_only then
575+ return ' https://api.githubcopilot.com/responses'
576+ end
462577 return ' https://api.githubcopilot.com/chat/completions'
463578 end ,
464579}
0 commit comments