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:
Aaron Pham 2024-08-29 23:36:39 -04:00 committed by GitHub
parent d82ef342c4
commit 483f71dba4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 175 additions and 7 deletions

View File

@ -41,6 +41,7 @@ https://github.com/user-attachments/assets/86140bfd-08b4-483d-a887-1b701d9e37dd
"MunifTanjim/nui.nvim",
--- The below dependencies are optional,
"nvim-tree/nvim-web-devicons", -- or echasnovski/mini.icons
"zbirenbaum/copilot.lua", -- for providers='copilot'
{
-- support for image pasting
"HakonHarnes/img-clip.nvim",
@ -86,6 +87,7 @@ Plug 'MunifTanjim/nui.nvim'
" Optional deps
Plug 'nvim-tree/nvim-web-devicons' "or Plug 'echasnovski/mini.icons'
Plug 'HakonHarnes/img-clip.nvim'
Plug 'zbirenbaum/copilot.lua'
" Yay
Plug 'yetone/avante.nvim'
@ -110,12 +112,14 @@ add({
},
})
--- optional
add({ source = 'zbirenbaum/copilot.lua' })
add({ source = 'HakonHarnes/img-clip.nvim' })
add({ source = 'MeanderingProgrammer/render-markdown.nvim' })
later(function() require('render-markdown').setup({...}) end)
later(function()
require('img-clip').setup({...}) -- config img-clip
require("copilot").setup({...}) -- setup copilot to your liking
require("avante").setup({...}) -- config for avante.nvim
end)
@ -134,6 +138,9 @@ end)
require('img-clip').setup ({
-- use recommended settings from above
})
require('copilot').setup ({
-- use recommended settings from above
})
require('render-markdown').setup ({
-- use recommended settings from above
})
@ -171,7 +178,7 @@ _See [config.lua#L9](./lua/avante/config.lua) for the full config_
```lua
{
---@alias Provider "openai" | "claude" | "azure" | "cohere" | [string]
provider = "claude", -- Only recommend using Claude
provider = "claude", -- Recommend using Claude
claude = {
endpoint = "https://api.anthropic.com",
model = "claude-3-5-sonnet-20240620",
@ -343,6 +350,7 @@ We would like to express our heartfelt gratitude to the contributors of the foll
| [git-conflict.nvim](https://github.com/akinsho/git-conflict.nvim) | No License | Diff comparison functionality | https://github.com/yetone/avante.nvim/blob/main/lua/avante/diff.lua |
| [ChatGPT.nvim](https://github.com/jackMort/ChatGPT.nvim) | Apache 2.0 License | Calculation of tokens count | https://github.com/yetone/avante.nvim/blob/main/lua/avante/utils/tokens.lua |
| [img-clip.nvim](https://github.com/HakonHarnes/img-clip.nvim) | MIT License | Clipboard image support | https://github.com/yetone/avante.nvim/blob/main/lua/avante/clipboard.lua |
| [copilot.lua](https://github.com/zbirenbaum/copilot.lua) | MIT License | Copilot support | https://github.com/yetone/avante.nvim/blob/main/lua/avante/providers/copilot.lua |
The high quality and ingenuity of these projects' source code have been immensely beneficial throughout our development process. We extend our sincere thanks and respect to the authors and contributors of these projects. It is the selfless dedication of the open-source community that drives projects like avante.nvim forward.

View File

@ -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"

View 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

View File

@ -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