diff --git a/Makefile b/Makefile index ebfe5768..7f378f18 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ else ifeq ($(UNAME), Windows_NT) else ifneq ($(findstring MSYS_NT,$(UNAME)),) OS := windows EXT := dll +else ifeq ($(UNAME), MINGW64_NT-10.0-26200) + OS := windows + EXT := dll else $(error Unsupported operating system: $(UNAME)) endif diff --git a/lua/CopilotChat/config.lua b/lua/CopilotChat/config.lua index 1e0adda3..39c87e89 100644 --- a/lua/CopilotChat/config.lua +++ b/lua/CopilotChat/config.lua @@ -54,6 +54,8 @@ ---@field functions table? ---@field prompts table? ---@field mappings CopilotChat.config.mappings? +---@field github_instance_url string? +---@field github_instance_api_url string? return { -- Shared config starts here (can be passed to functions at runtime and configured via setup function) @@ -115,6 +117,9 @@ return { selection = 'visual', -- Selection source chat_autocomplete = true, -- Enable chat autocompletion (when disabled, requires manual `mappings.complete` trigger) + github_instance_url = 'bbraun.ghe.com', + github_instance_api_url = 'api.bbraun.ghe.com', + log_path = vim.fn.stdpath('state') .. '/CopilotChat.log', -- Default path to log file history_path = vim.fn.stdpath('data') .. '/copilotchat_history', -- Default path to stored history diff --git a/lua/CopilotChat/config/providers.lua b/lua/CopilotChat/config/providers.lua index 3df37667..7184e885 100644 --- a/lua/CopilotChat/config/providers.lua +++ b/lua/CopilotChat/config/providers.lua @@ -8,6 +8,19 @@ local files = require('CopilotChat.utils.files') local EDITOR_VERSION = 'Neovim/' .. vim.version().major .. '.' .. vim.version().minor .. '.' .. vim.version().patch +---@class CopilotChat +---@field config CopilotChat.config.Config +---@field chat CopilotChat.ui.chat.Chat +local MC = setmetatable({}, { + __index = function(t, key) + if key == 'config' then + return require('CopilotChat.config') + end + return rawget(t, key) + end, +}) + + local token_cache = nil local unsaved_token_cache = {} local function load_tokens() @@ -57,7 +70,7 @@ end ---@return string local function github_device_flow(tag, client_id, scope) local function request_device_code() - local res = curl.post('https://github.com/login/device/code', { + local res = curl.post('https://' .. MC.config.github_instance_api_url .. '/login/device/code', { body = { client_id = client_id, scope = scope, @@ -72,7 +85,7 @@ local function github_device_flow(tag, client_id, scope) local function poll_for_token(device_code, interval) plenary_utils.sleep(interval * 1000) - local res = curl.post('https://github.com/login/oauth/access_token', { + local res = curl.post('https://' .. MC.config.github_instance_api_url .. '/login/oauth/access_token', { json_response = true, body = { client_id = client_id, @@ -157,7 +170,7 @@ local function get_github_copilot_token(tag) local parsed_data = utils.json_decode(file_data) if parsed_data then for key, value in pairs(parsed_data) do - if string.find(key, 'github.com') and value and value.oauth_token then + if string.find(key, MC.config.github_instance_url) and value and value.oauth_token then return set_token(tag, value.oauth_token, false) end end @@ -184,7 +197,7 @@ local function get_github_models_token(tag) -- loading token from gh cli if available if vim.fn.executable('gh') == 0 then - local result = utils.system({ 'gh', 'auth', 'token', '-h', 'github.com' }) + local result = utils.system({ 'gh', 'auth', 'token', '-h', MC.config.github_instance_url }) if result and result.code == 0 and result.stdout then local gh_token = vim.trim(result.stdout) if gh_token ~= '' and not gh_token:find('no oauth token') then @@ -520,16 +533,19 @@ end ---@field prepare_input nil|fun(inputs:CopilotChat.client.Message[], opts:CopilotChat.config.providers.Options):table,table? ---@field prepare_output nil|fun(output:table, opts:CopilotChat.config.providers.Options):CopilotChat.config.providers.Output ---@field get_url nil|fun(opts:CopilotChat.config.providers.Options):string +---@field endpoints_api string? ---@type table local M = {} M.copilot = { + endpoints_api = '', + get_headers = function() - local response, err = curl.get('https://api.github.com/copilot_internal/v2/token', { + local response, err = curl.get('https://' .. MC.config.github_instance_api_url ..'/copilot_internal/v2/token', { json_response = true, headers = { - ['Authorization'] = 'Token ' .. get_github_copilot_token('github_copilot'), + ['Authorization'] = 'Token ' .. get_github_copilot_token(MC.config.github_instance_api_url), }, }) @@ -537,6 +553,21 @@ M.copilot = { error(err) end + if response.body and response.body.endpoints and response.body.endpoints.api then + log.info('get_headers ok, authenticated. Use api endpoint: ' .. response.body.endpoints.api) + M.endpoints_api = response.body.endpoints.api + else + log.error( + 'get_headers authenticated, but missing key "endpoints.api" in server response. response: ' + .. utils.to_string(response) + ) + error( + 'get_headers authenticated, but missing key "endpoints.api" in server response. Check log for details: ' + .. MC.config.log_path + ) + end + + return { ['Authorization'] = 'Bearer ' .. response.body.token, ['Editor-Version'] = EDITOR_VERSION, @@ -548,10 +579,10 @@ M.copilot = { end, get_info = function() - local response, err = curl.get('https://api.github.com/copilot_internal/user', { + local response, err = curl.get('https://' .. MC.config.github_instance_url .. '/copilot_internal/user', { json_response = true, headers = { - ['Authorization'] = 'Token ' .. get_github_copilot_token('github_copilot'), + ['Authorization'] = 'Token ' .. get_github_copilot_token(MC.config.github_instance_api_url), }, }) @@ -598,7 +629,7 @@ M.copilot = { end, get_models = function(headers) - local response, err = curl.get('https://api.githubcopilot.com/models', { + local response, err = curl.get(M.endpoints_api .. '/models', { json_response = true, headers = headers, }) @@ -643,7 +674,7 @@ M.copilot = { for _, model in ipairs(models) do if not model.policy then - pcall(curl.post, 'https://api.githubcopilot.com/models/' .. model.id .. '/policy', { + pcall(curl.post, M.endpoints_api .. '/models/' .. model.id .. '/policy', { headers = headers, json_request = true, body = { state = 'enabled' }, @@ -666,7 +697,7 @@ M.copilot = { return model end - local url = 'https://api.githubcopilot.com/models/session' + local url = M.endpoints_api .. '/models/session' local response, err = curl.post(url, { headers = headers, body = { auto_mode = { model_hints = { 'auto' } } }, @@ -708,9 +739,9 @@ M.copilot = { get_url = function(opts) if opts and opts.model and opts.model.use_responses then - return 'https://api.githubcopilot.com/responses' + return M.endpoints_api ..'/responses' end - return 'https://api.githubcopilot.com/chat/completions' + return M.endpoints_api ..'/chat/completions' end, } @@ -724,7 +755,7 @@ M.github_models = { end, get_models = function(headers) - local response, err = curl.get('https://models.github.ai/catalog/models', { + local response, err = curl.get('https://api.bbraun.ghe.com/models', { json_response = true, headers = headers, }) @@ -755,7 +786,7 @@ M.github_models = { prepare_output = M.copilot.prepare_output, get_url = function() - return 'https://models.github.ai/inference/chat/completions' + return 'https://api.bbraun.ghe.com/models/inference/chat/completions' end, }