From d45b62219323d1f9f7cf36a3808cd4e97166a6ce Mon Sep 17 00:00:00 2001 From: yetone Date: Thu, 30 Oct 2025 22:38:32 +0800 Subject: [PATCH] Auto select Copilot Response API for GPT-5 Codex models (#2808) --- lua/avante/config.lua | 9 +++++++-- lua/avante/providers/copilot.lua | 10 +++++++--- lua/avante/providers/init.lua | 16 ++++++++++++++++ lua/avante/providers/openai.lua | 20 +++++++++++--------- lua/avante/sidebar.lua | 4 +++- lua/avante/types.lua | 2 +- 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lua/avante/config.lua b/lua/avante/config.lua index 6807daf..ad32f0c 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -6,6 +6,11 @@ local Utils = require("avante.utils") +local function copilot_use_response_api(opts) + local model = opts and opts.model + return type(model) == "string" and model:match("gpt%-5%-codex") ~= nil +end + ---@class avante.file_selector.IParams ---@field public title string ---@field public filepaths string[] @@ -286,7 +291,7 @@ M._defaults = { model = "gpt-4o", timeout = 30000, -- Timeout in milliseconds, increase this for reasoning models context_window = 128000, -- Number of tokens to send to the model for context - use_response_api = false, -- Set to true to use OpenAI's new Response API (/responses) instead of Chat Completions API (/chat/completions) + use_response_api = copilot_use_response_api, -- Automatically switch to Response API for GPT-5 Codex models support_previous_response_id = true, -- OpenAI Response API supports previous_response_id for stateful conversations -- NOTE: Response API automatically manages conversation state using previous_response_id for tool calling extra_request_body = { @@ -305,7 +310,7 @@ M._defaults = { allow_insecure = false, -- Allow insecure server connections timeout = 30000, -- Timeout in milliseconds context_window = 64000, -- Number of tokens to send to the model for context - use_response_api = true, -- Copilot uses Response API input format + use_response_api = copilot_use_response_api, -- Automatically switch to Response API for GPT-5 Codex models support_previous_response_id = false, -- Copilot doesn't support previous_response_id, must send full history -- NOTE: Copilot doesn't support previous_response_id, always sends full conversation history including tool_calls -- NOTE: Response API doesn't support some parameters like top_p, frequency_penalty, presence_penalty diff --git a/lua/avante/providers/copilot.lua b/lua/avante/providers/copilot.lua index 4ca4087..b1eccff 100644 --- a/lua/avante/providers/copilot.lua +++ b/lua/avante/providers/copilot.lua @@ -282,6 +282,7 @@ function M:parse_curl_args(prompt_opts) H.refresh_token(false, false) local provider_conf, request_body = Providers.parse_config(self) + local use_response_api = Providers.resolve_use_response_api(provider_conf, prompt_opts) local disable_tools = provider_conf.disable_tools or false -- Apply OpenAI's set_allowed_params for Response API compatibility @@ -295,7 +296,7 @@ function M:parse_curl_args(prompt_opts) for _, tool in ipairs(prompt_opts.tools) do local transformed_tool = OpenAI:transform_tool(tool) -- Response API uses flattened tool structure - if provider_conf.use_response_api then + if use_response_api then if transformed_tool.type == "function" and transformed_tool["function"] then transformed_tool = { type = "function", @@ -328,7 +329,7 @@ function M:parse_curl_args(prompt_opts) -- Response API uses 'input' instead of 'messages' -- NOTE: Copilot doesn't support previous_response_id, always send full history - if provider_conf.use_response_api then + if use_response_api then base_body.input = parsed_messages -- Response API uses max_output_tokens instead of max_tokens/max_completion_tokens @@ -354,8 +355,11 @@ function M:parse_curl_args(prompt_opts) } end + local base_url = M.state.github_token.endpoints.api or provider_conf.endpoint + local build_url = use_response_api and H.response_url or H.chat_completion_url + return { - url = H.response_url(M.state.github_token.endpoints.api or provider_conf.endpoint), + url = build_url(base_url), timeout = provider_conf.timeout, proxy = provider_conf.proxy, insecure = provider_conf.allow_insecure, diff --git a/lua/avante/providers/init.lua b/lua/avante/providers/init.lua index d331297..d4b1765 100644 --- a/lua/avante/providers/init.lua +++ b/lua/avante/providers/init.lua @@ -256,6 +256,22 @@ function M.parse_config(opts) return provider_opts, request_body end +---@param provider_conf table | nil +---@param ctx any +---@return boolean +function M.resolve_use_response_api(provider_conf, ctx) + if not provider_conf then return false end + local value = provider_conf.use_response_api + if type(value) ~= "function" then value = provider_conf._use_response_api_resolver or value end + if type(value) == "function" then + provider_conf._use_response_api_resolver = value + local ok, result = pcall(value, provider_conf, ctx) + if not ok then error("Failed to evaluate use_response_api: " .. result, 2) end + return result == true + end + return value == true +end + ---@param provider_name avante.ProviderName function M.get_config(provider_name) provider_name = provider_name or Config.provider diff --git a/lua/avante/providers/openai.lua b/lua/avante/providers/openai.lua index 7ba5645..ddc1a15 100644 --- a/lua/avante/providers/openai.lua +++ b/lua/avante/providers/openai.lua @@ -71,11 +71,12 @@ function M.is_reasoning_model(model) end function M.set_allowed_params(provider_conf, request_body) + local use_response_api = Providers.resolve_use_response_api(provider_conf, nil) if M.is_reasoning_model(provider_conf.model) then -- Reasoning models have specific parameter requirements request_body.temperature = 1 -- Response API doesn't support temperature for reasoning models - if provider_conf.use_response_api then request_body.temperature = nil end + if use_response_api then request_body.temperature = nil end else request_body.reasoning_effort = nil request_body.reasoning = nil @@ -84,7 +85,7 @@ function M.set_allowed_params(provider_conf, request_body) if request_body.max_tokens then request_body.max_completion_tokens = nil end -- Handle Response API specific parameters - if provider_conf.use_response_api then + if use_response_api then -- Convert reasoning_effort to reasoning object for Response API if request_body.reasoning_effort then request_body.reasoning = { @@ -113,6 +114,7 @@ end function M:parse_messages(opts) local messages = {} local provider_conf, _ = Providers.parse_config(self) + local use_response_api = Providers.resolve_use_response_api(provider_conf, opts) local use_ReAct_prompt = provider_conf.use_ReAct_prompt == true local system_prompt = opts.system_prompt @@ -209,13 +211,12 @@ function M:parse_messages(opts) if #tool_calls > 0 then -- Only skip tool_calls if using Response API with previous_response_id support -- Copilot uses Response API format but doesn't support previous_response_id - local should_include_tool_calls = not provider_conf.use_response_api - or not provider_conf.support_previous_response_id + local should_include_tool_calls = not use_response_api or not provider_conf.support_previous_response_id if should_include_tool_calls then -- For Response API without previous_response_id support (like Copilot), -- convert tool_calls to function_call items in input - if provider_conf.use_response_api then + if use_response_api then for _, tool_call in ipairs(tool_calls) do table.insert(messages, { type = "function_call", @@ -242,7 +243,7 @@ function M:parse_messages(opts) if #tool_results > 0 then for _, tool_result in ipairs(tool_results) do -- Response API uses different format for function outputs - if provider_conf.use_response_api then + if use_response_api then table.insert(messages, { type = "function_call_output", call_id = tool_result.tool_call_id, @@ -741,6 +742,7 @@ function M:parse_curl_args(prompt_opts) end self.set_allowed_params(provider_conf, request_body) + local use_response_api = Providers.resolve_use_response_api(provider_conf, prompt_opts) local use_ReAct_prompt = provider_conf.use_ReAct_prompt == true @@ -750,7 +752,7 @@ function M:parse_curl_args(prompt_opts) for _, tool in ipairs(prompt_opts.tools) do local transformed_tool = self:transform_tool(tool) -- Response API uses flattened tool structure - if provider_conf.use_response_api then + if use_response_api then -- Convert from {type: "function", function: {name, description, parameters}} -- to {type: "function", name, description, parameters} if transformed_tool.type == "function" and transformed_tool["function"] then @@ -773,7 +775,7 @@ function M:parse_curl_args(prompt_opts) if use_ReAct_prompt then stop = { "" } end -- Determine endpoint path based on use_response_api - local endpoint_path = provider_conf.use_response_api and "/responses" or "/chat/completions" + local endpoint_path = use_response_api and "/responses" or "/chat/completions" local parsed_messages = self:parse_messages(prompt_opts) @@ -786,7 +788,7 @@ function M:parse_curl_args(prompt_opts) } -- Response API uses 'input' instead of 'messages' - if provider_conf.use_response_api then + if use_response_api then -- Check if we have tool results - if so, use previous_response_id local has_function_outputs = false for _, msg in ipairs(parsed_messages) do diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index 820215b..c38db0f 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -2527,8 +2527,10 @@ function Sidebar:get_history_messages_for_api(opts) end if not Config.acp_providers[Config.provider] then + local provider = Providers[Config.provider] + local use_response_api = Providers.resolve_use_response_api(provider, nil) local tool_limit - if Providers[Config.provider].use_ReAct_prompt or Providers[Config.provider].use_response_api then + if provider.use_ReAct_prompt or use_response_api then tool_limit = nil else tool_limit = 25 diff --git a/lua/avante/types.lua b/lua/avante/types.lua index 1a2955c..4c68499 100644 --- a/lua/avante/types.lua +++ b/lua/avante/types.lua @@ -263,7 +263,7 @@ vim.g.avante_login = vim.g.avante_login ---@field hide_in_model_selector? boolean ---@field use_ReAct_prompt? boolean ---@field context_window? integer ----@field use_response_api? boolean +---@field use_response_api? boolean | fun(provider: AvanteDefaultBaseProvider, ctx?: any): boolean ---@field support_previous_response_id? boolean --- ---@class AvanteSupportedProvider: AvanteDefaultBaseProvider