feat(provider): support copilot (#381)
* feat(provider): add back support for copilot Signed-off-by: Aaron Pham <contact@aarnphm.xyz> * docs: add acknowledgement Signed-off-by: Aaron Pham <contact@aarnphm.xyz> --------- Signed-off-by: Aaron Pham <contact@aarnphm.xyz>
This commit is contained in:
@@ -21,6 +21,16 @@ M.defaults = {
|
||||
max_tokens = 4096,
|
||||
["local"] = false,
|
||||
},
|
||||
---@type AvanteSupportedProvider
|
||||
copilot = {
|
||||
endpoint = "https://api.githubcopilot.com",
|
||||
model = "gpt-4o-2024-05-13",
|
||||
proxy = nil, -- [protocol://]host[:port] Use this proxy
|
||||
allow_insecure = false, -- Allow insecure server connections
|
||||
timeout = 30000, -- Timeout in milliseconds
|
||||
temperature = 0,
|
||||
max_tokens = 4096,
|
||||
},
|
||||
---@type AvanteAzureProvider
|
||||
azure = {
|
||||
endpoint = "", -- example: "https://<your-resource-name>.openai.azure.com"
|
||||
|
||||
156
lua/avante/providers/copilot.lua
Normal file
156
lua/avante/providers/copilot.lua
Normal file
@@ -0,0 +1,156 @@
|
||||
---Reference implementation:
|
||||
---https://github.com/zbirenbaum/copilot.lua/blob/master/lua/copilot/auth.lua config file
|
||||
---https://github.com/zed-industries/zed/blob/ad43bbbf5eda59eba65309735472e0be58b4f7dd/crates/copilot/src/copilot_chat.rs#L272 for authorization
|
||||
---
|
||||
---@class CopilotToken
|
||||
---@field annotations_enabled boolean
|
||||
---@field chat_enabled boolean
|
||||
---@field chat_jetbrains_enabled boolean
|
||||
---@field code_quote_enabled boolean
|
||||
---@field codesearch boolean
|
||||
---@field copilotignore_enabled boolean
|
||||
---@field endpoints {api: string, ["origin-tracker"]: string, proxy: string, telemetry: string}
|
||||
---@field expires_at integer
|
||||
---@field individual boolean
|
||||
---@field nes_enabled boolean
|
||||
---@field prompt_8k boolean
|
||||
---@field public_suggestions string
|
||||
---@field refresh_in integer
|
||||
---@field sku string
|
||||
---@field snippy_load_test_enabled boolean
|
||||
---@field telemetry string
|
||||
---@field token string
|
||||
---@field tracking_id string
|
||||
---@field vsc_electron_fetcher boolean
|
||||
---@field xcode boolean
|
||||
---@field xcode_chat boolean
|
||||
|
||||
local curl = require("plenary.curl")
|
||||
|
||||
local Config = require("avante.config")
|
||||
local Path = require("plenary.path")
|
||||
local Utils = require("avante.utils")
|
||||
local P = require("avante.providers")
|
||||
local O = require("avante.providers").openai
|
||||
|
||||
local H = {}
|
||||
|
||||
---@class OAuthToken
|
||||
---@field user string
|
||||
---@field oauth_token string
|
||||
---
|
||||
---@return string
|
||||
H.get_oauth_token = function()
|
||||
local xdg_config = vim.fn.expand("$XDG_CONFIG_HOME")
|
||||
local os_name = Utils.get_os_name()
|
||||
---@type string
|
||||
local config_dir
|
||||
|
||||
if vim.tbl_contains({ "linux", "darwin" }, os_name) then
|
||||
config_dir = vim.fn.isdirectory(xdg_config) and xdg_config or vim.fn.expand("~/.config")
|
||||
else
|
||||
config_dir = vim.fn.expand("~/AppData/Local")
|
||||
end
|
||||
|
||||
local yason = Path:new(config_dir):joinpath("github-copilot", "hosts.json")
|
||||
if not yason:exists() then
|
||||
error("You must setup copilot with either copilot.lua or copilot.vim", 2)
|
||||
end
|
||||
return vim
|
||||
.iter(
|
||||
---@type table<string, OAuthToken>
|
||||
vim.json.decode(yason:read())
|
||||
)
|
||||
:filter(function(k, _)
|
||||
return k:match("github.com")
|
||||
end)
|
||||
---@param acc {oauth_token: string}
|
||||
:fold({}, function(acc, _, v)
|
||||
acc.oauth_token = v.oauth_token
|
||||
return acc
|
||||
end)
|
||||
.oauth_token
|
||||
end
|
||||
|
||||
H.chat_auth_url = "https://api.github.com/copilot_internal/v2/token"
|
||||
H.chat_completion_url = function(base_url)
|
||||
return Utils.trim(base_url, { prefix = "/" }) .. "/chat/completions"
|
||||
end
|
||||
|
||||
---@class AvanteProviderFunctor
|
||||
local M = {}
|
||||
|
||||
H.refresh_token = function()
|
||||
if not M.state then
|
||||
error("internal initialization error")
|
||||
end
|
||||
|
||||
if
|
||||
not M.state.github_token
|
||||
or (M.state.github_token.expires_at and M.state.github_token.expires_at < math.floor(os.time()))
|
||||
then
|
||||
curl.get(H.chat_auth_url, {
|
||||
headers = {
|
||||
["Authorization"] = "token " .. M.state.oauth_token,
|
||||
["Accept"] = "application/json",
|
||||
},
|
||||
timeout = Config.copilot.timeout,
|
||||
proxy = Config.copilot.proxy,
|
||||
insecure = Config.copilot.allow_insecure,
|
||||
on_error = function(err)
|
||||
error("Failed to get response: " .. vim.inspect(err))
|
||||
end,
|
||||
callback = function(output)
|
||||
M.state.github_token = vim.json.decode(output.body)
|
||||
if not vim.g.avante_login then
|
||||
vim.g.avante_login = true
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@class AvanteCopilotState
|
||||
---@field oauth_token string
|
||||
---@field github_token CopilotToken?
|
||||
M.state = nil
|
||||
|
||||
M.api_key_name = P.AVANTE_INTERNAL_KEY
|
||||
|
||||
M.parse_message = O.parse_message
|
||||
M.parse_response = O.parse_response
|
||||
|
||||
M.parse_curl_args = function(provider, code_opts)
|
||||
H.refresh_token()
|
||||
|
||||
local base, body_opts = P.parse_config(provider)
|
||||
|
||||
return {
|
||||
url = H.chat_completion_url(base.endpoint),
|
||||
timeout = base.timeout,
|
||||
proxy = base.proxy,
|
||||
insecure = base.allow_insecure,
|
||||
headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
["Authorization"] = "Bearer " .. M.state.github_token.token,
|
||||
["Copilot-Integration-Id"] = "vscode-chat",
|
||||
["Editor-Version"] = ("Neovim/%s.%s.%s"):format(vim.version().major, vim.version().minor, vim.version().patch),
|
||||
},
|
||||
body = vim.tbl_deep_extend("force", {
|
||||
model = base.model,
|
||||
messages = M.parse_message(code_opts),
|
||||
stream = true,
|
||||
}, body_opts),
|
||||
}
|
||||
end
|
||||
|
||||
M.setup = function()
|
||||
if not M.state then
|
||||
M.state = { github_token = nil, oauth_token = H.get_oauth_token() }
|
||||
H.refresh_token()
|
||||
end
|
||||
vim.g.avante_login = true
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -370,12 +370,6 @@ end
|
||||
---@return AvanteProviderFunctor
|
||||
M.get_config = function(provider)
|
||||
provider = provider or Config.provider
|
||||
if provider == "copilot" then
|
||||
Utils.error(
|
||||
"Sorry! We no longer support the copilot provider! Please use other providers!",
|
||||
{ once = true, title = "Avante" }
|
||||
)
|
||||
end
|
||||
local cur = Config.get_provider(provider)
|
||||
return type(cur) == "function" and cur() or cur
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user