2024-08-29 23:36:39 -04:00
|
|
|
---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
|
|
|
|
|
2024-09-03 05:12:07 -04:00
|
|
|
local curl = require("plenary.curl")
|
2024-08-29 23:36:39 -04:00
|
|
|
|
2024-09-03 05:12:07 -04:00
|
|
|
local Config = require("avante.config")
|
|
|
|
local Path = require("plenary.path")
|
|
|
|
local Utils = require("avante.utils")
|
|
|
|
local P = require("avante.providers")
|
2024-08-29 23:36:39 -04:00
|
|
|
local O = require("avante.providers").openai
|
|
|
|
|
|
|
|
local H = {}
|
|
|
|
|
2024-10-22 04:37:17 -04:00
|
|
|
local copilot_path = vim.fn.stdpath("data") .. "/avante/github-copilot.json"
|
|
|
|
|
2024-08-29 23:36:39 -04:00
|
|
|
---@class OAuthToken
|
|
|
|
---@field user string
|
|
|
|
---@field oauth_token string
|
|
|
|
---
|
|
|
|
---@return string
|
|
|
|
H.get_oauth_token = function()
|
2024-09-03 05:12:07 -04:00
|
|
|
local xdg_config = vim.fn.expand("$XDG_CONFIG_HOME")
|
2024-08-29 23:36:39 -04:00
|
|
|
local os_name = Utils.get_os_name()
|
|
|
|
---@type string
|
|
|
|
local config_dir
|
|
|
|
|
|
|
|
if vim.tbl_contains({ "linux", "darwin" }, os_name) then
|
2024-09-03 05:12:07 -04:00
|
|
|
config_dir = (xdg_config and vim.fn.isdirectory(xdg_config) > 0) and xdg_config or vim.fn.expand("~/.config")
|
2024-08-29 23:36:39 -04:00
|
|
|
else
|
2024-09-03 05:12:07 -04:00
|
|
|
config_dir = vim.fn.expand("~/AppData/Local")
|
2024-08-29 23:36:39 -04:00
|
|
|
end
|
|
|
|
|
2024-08-30 15:12:20 -04:00
|
|
|
--- hosts.json (copilot.lua), apps.json (copilot.vim)
|
|
|
|
---@type Path[]
|
|
|
|
local paths = vim.iter({ "hosts.json", "apps.json" }):fold({}, function(acc, path)
|
|
|
|
local yason = Path:new(config_dir):joinpath("github-copilot", path)
|
2024-09-03 04:19:54 -04:00
|
|
|
if yason:exists() then table.insert(acc, yason) end
|
2024-08-30 15:12:20 -04:00
|
|
|
return acc
|
|
|
|
end)
|
2024-09-03 04:19:54 -04:00
|
|
|
if #paths == 0 then error("You must setup copilot with either copilot.lua or copilot.vim", 2) end
|
2024-08-30 15:12:20 -04:00
|
|
|
|
|
|
|
local yason = paths[1]
|
2024-08-29 23:36:39 -04:00
|
|
|
return vim
|
|
|
|
.iter(
|
|
|
|
---@type table<string, OAuthToken>
|
|
|
|
vim.json.decode(yason:read())
|
|
|
|
)
|
2024-09-03 05:12:07 -04:00
|
|
|
:filter(function(k, _) return k:match("github.com") end)
|
2024-08-29 23:36:39 -04:00
|
|
|
---@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"
|
2024-11-19 06:20:42 +08:00
|
|
|
H.chat_completion_url = function(base_url) return Utils.url_join(base_url, "/chat/completions") end
|
2024-08-29 23:36:39 -04:00
|
|
|
|
|
|
|
---@class AvanteProviderFunctor
|
|
|
|
local M = {}
|
|
|
|
|
|
|
|
H.refresh_token = function()
|
2024-09-03 05:12:07 -04:00
|
|
|
if not M.state then error("internal initialization error") end
|
2024-08-29 23:36:39 -04:00
|
|
|
|
|
|
|
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
|
2024-12-01 14:00:42 +08:00
|
|
|
curl.get(H.chat_auth_url, {
|
2024-08-29 23:36:39 -04:00
|
|
|
headers = {
|
|
|
|
["Authorization"] = "token " .. M.state.oauth_token,
|
|
|
|
["Accept"] = "application/json",
|
|
|
|
},
|
|
|
|
timeout = Config.copilot.timeout,
|
|
|
|
proxy = Config.copilot.proxy,
|
|
|
|
insecure = Config.copilot.allow_insecure,
|
2024-12-01 14:00:42 +08:00
|
|
|
callback = function(response)
|
|
|
|
if response.status == 200 then
|
|
|
|
M.state.github_token = vim.json.decode(response.body)
|
|
|
|
local file = Path:new(copilot_path)
|
|
|
|
file:write(vim.json.encode(M.state.github_token), "w")
|
|
|
|
if not vim.g.avante_login then vim.g.avante_login = true end
|
|
|
|
else
|
|
|
|
error("Failed to get success response: " .. vim.inspect(response))
|
|
|
|
end
|
|
|
|
end,
|
2024-08-29 23:36:39 -04:00
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
---@private
|
|
|
|
---@class AvanteCopilotState
|
|
|
|
---@field oauth_token string
|
|
|
|
---@field github_token CopilotToken?
|
|
|
|
M.state = nil
|
|
|
|
|
2024-11-17 02:55:40 +08:00
|
|
|
M.api_key_name = ""
|
2024-08-31 13:39:50 -04:00
|
|
|
M.tokenizer_id = "gpt-4o"
|
2024-11-04 16:20:28 +08:00
|
|
|
M.role_map = {
|
|
|
|
user = "user",
|
|
|
|
assistant = "assistant",
|
|
|
|
}
|
2024-08-29 23:36:39 -04:00
|
|
|
|
2024-11-04 16:20:28 +08:00
|
|
|
M.parse_messages = function(opts)
|
|
|
|
local messages = {
|
2024-08-30 11:59:40 -04:00
|
|
|
{ role = "system", content = opts.system_prompt },
|
|
|
|
}
|
2024-11-04 16:20:28 +08:00
|
|
|
vim
|
|
|
|
.iter(opts.messages)
|
|
|
|
:each(function(msg) table.insert(messages, { role = M.role_map[msg.role], content = msg.content }) end)
|
|
|
|
return messages
|
2024-08-30 11:59:40 -04:00
|
|
|
end
|
2024-08-30 15:12:20 -04:00
|
|
|
|
2024-08-29 23:36:39 -04:00
|
|
|
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,
|
2024-11-04 16:20:28 +08:00
|
|
|
messages = M.parse_messages(code_opts),
|
2024-08-29 23:36:39 -04:00
|
|
|
stream = true,
|
|
|
|
}, body_opts),
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
M.setup = function()
|
2024-10-22 04:37:17 -04:00
|
|
|
local copilot_token_file = Path:new(copilot_path)
|
|
|
|
|
2024-08-29 23:36:39 -04:00
|
|
|
if not M.state then
|
2024-10-22 04:37:17 -04:00
|
|
|
M.state = {
|
|
|
|
github_token = copilot_token_file:exists() and vim.json.decode(copilot_token_file:read()) or nil,
|
|
|
|
oauth_token = H.get_oauth_token(),
|
|
|
|
}
|
2024-08-29 23:36:39 -04:00
|
|
|
end
|
2024-11-06 15:07:02 +10:00
|
|
|
|
|
|
|
vim.schedule(function() H.refresh_token() end)
|
2024-10-22 04:37:17 -04:00
|
|
|
|
2024-08-31 13:39:50 -04:00
|
|
|
require("avante.tokenizers").setup(M.tokenizer_id)
|
2024-08-29 23:36:39 -04:00
|
|
|
vim.g.avante_login = true
|
|
|
|
end
|
|
|
|
|
|
|
|
return M
|