Skip to content

Commit

Permalink
feat(provider): support copilot (yetone#381)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
aarnphm authored Aug 30, 2024
1 parent d82ef34 commit 483f71d
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 7 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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'
Expand All @@ -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)

Expand All @@ -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
})
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions lua/avante/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
156 changes: 156 additions & 0 deletions lua/avante/providers/copilot.lua
Original file line number Diff line number Diff line change
@@ -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
6 changes: 0 additions & 6 deletions lua/avante/providers/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 483f71d

Please sign in to comment.