Skip to content

Expose is_authenticated() helper #519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions lua/copilot/auth/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ function M.signout()
end)
end

---@return string|nil
function M.find_config_path()
local config = vim.fn.expand("$XDG_CONFIG_HOME")
if config and vim.fn.isdirectory(config) > 0 then
Expand All @@ -151,6 +152,7 @@ function M.find_config_path()
logger.error("could not find config path")
end

---@return table|nil
M.get_creds = function()
local filename = M.find_config_path() .. "/github-copilot/apps.json"

Expand Down Expand Up @@ -180,4 +182,65 @@ function M.info()
logger.notify("GitHub Copilot token information: ", info)
end

---@class copilot_auth_cache
---@field authenticated boolean|nil
---@field timestamp number

---@type copilot_auth_cache
local auth_cache = {
authenticated = nil,
timestamp = 0,
}

---@param authenticated boolean
---@return number
local function get_cache_ttl(authenticated)
if authenticated then
return 300000 -- 5 minutes for true status
else
return 30000 -- 30 seconds for false status
end
end

---@param client vim.lsp.Client
---@param callback? fun()
local function check_status(client, callback)
api.check_status(client, {}, function(err, status)
auth_cache.timestamp = vim.loop.now()

if not err and status and status.user then
auth_cache.authenticated = true
else
auth_cache.authenticated = false
end

if callback then
callback()
end
end)
end

---@param callback? fun()
function M.is_authenticated(callback)
local current_time = vim.loop.now()

if auth_cache.authenticated ~= nil then
local ttl = get_cache_ttl(auth_cache.authenticated)
if (current_time - auth_cache.timestamp) < ttl then
return auth_cache.authenticated
end
end

local client = c.get()
if not client then
auth_cache.authenticated = false
auth_cache.timestamp = current_time
return false
end

check_status(client, callback)

return auth_cache.authenticated or false
end

return M
93 changes: 93 additions & 0 deletions lua/copilot/health.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
local M = {}

local api = require("copilot.api")
local auth = require("copilot.auth")
local c = require("copilot.client")
local config = require("copilot.config")

local start = vim.health.start or vim.health.report_start
local ok = vim.health.ok or vim.health.report_ok
local warn = vim.health.warn or vim.health.report_warn
local error = vim.health.error or vim.health.report_error
local info = vim.health.info or vim.health.report_info

function M.check()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a screenshot of what this current healthcheck setup looks like:
Screenshot 2025-07-25 at 12 19 47 PM

start("{copilot.lua}")
info("{copilot.lua} GitHub Copilot plugin for Neovim")

start("Copilot Dependencies")

if vim.fn.executable("node") == 1 then
local node_version = vim.fn.system("node --version"):gsub("\n", "")
ok("`node` found: " .. node_version)
else
error("`node` not found in PATH")
info("Install Node.js from https://nodejs.org")
end

start("Copilot Authentication")

local github_token = os.getenv("GITHUB_COPILOT_TOKEN")
local gh_token = os.getenv("GH_COPILOT_TOKEN")
if github_token or gh_token then
ok("Environment token found: " .. (github_token and "`GITHUB_COPILOT_TOKEN`" or "`GH_COPILOT_TOKEN`"))
else
info("No environment token set (`GITHUB_COPILOT_TOKEN` or `GH_COPILOT_TOKEN`)")
end

local config_path = auth.find_config_path()
local creds_ok, creds = pcall(auth.get_creds)
if creds_ok and creds then
ok("Local credentials file found")
info("Location: `" .. (config_path or "unknown") .. "/github-copilot/apps.json`")
else
info("No local credentials file found")
info("Expected location: `" .. (config_path or "unknown") .. "/github-copilot/apps.json`")
info("Run `:Copilot auth` to authenticate")
end

local client = c.get()
if not client then
error("Copilot LSP client not available")
info("Check that the plugin is properly loaded and configured")
info("Or restart Neovim if the plugin was just installed")
return
end

start("Copilot LSP Status")
ok("LSP client is available and running")
info("Client ID: " .. tostring(client.id))

local lsp_authenticated = auth.is_authenticated()
if lsp_authenticated then
ok("LSP authentication status: authenticated")
else
warn("LSP authentication status: not authenticated")
end
info("For detailed authentication status, run `:Copilot status`")

start("Copilot Configuration")
local suggestion_config = config.suggestion
if suggestion_config and suggestion_config.enabled ~= false then
ok("Suggestions enabled")
if suggestion_config.auto_trigger ~= false then
info("Auto-trigger: enabled")
else
info("Auto-trigger: disabled (manual trigger only)")
end
else
warn("Suggestions disabled in configuration")
info("Enable with `suggestion = { enabled = true }` in setup()")
end

local panel_config = config.panel
if panel_config and panel_config.enabled ~= false then
ok("Panel enabled")
info("Panel Keybinding: " .. (panel_config.keymap and panel_config.keymap.open or "<M-CR>"))
else
info("Panel disabled in configuration")
info("Enable with `panel = { enabled = true }` in setup()")
end
end

return M
10 changes: 9 additions & 1 deletion lua/copilot/suggestion/init.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local api = require("copilot.api")
local auth = require("copilot.auth")
local c = require("copilot.client")
local config = require("copilot.config")
local hl_group = require("copilot.highlight").group
Expand Down Expand Up @@ -484,10 +485,17 @@ local function advance(count, ctx)
end

local function schedule(ctx)
if not is_enabled() or not c.initialized then
-- in case we need to call the lsp API to know if we are authenticated,
-- we want to retry this method after the authentication check
local is_authenticated = auth.is_authenticated(function()
schedule(ctx)
end)

if not is_enabled() or not c.initialized or not is_authenticated then
clear()
return
end

logger.trace("suggestion schedule", ctx)

if copilot._copilot_timer then
Expand Down
45 changes: 45 additions & 0 deletions tests/test_auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,49 @@ T["auth()"]["auth issue replication"] = function()
u.expect_match(messages, ".*Online.*Enabled.*")
end

T["auth()"]["is_authenticated when not authed returns false"] = function()
child.configure_copilot()

local result = child.lua([[
local auth = require("copilot.auth")
local auth_result = auth.is_authenticated()
return tostring(auth_result)
]])

u.expect_match(result, "false")
end

T["auth()"]["is_authenticated when authed returns true"] = function()
child.configure_copilot()
child.cmd("Copilot auth")

child.lua([[
local messages = ""
local function has_passed()
messages = vim.api.nvim_exec("messages", { output = true }) or ""
return string.find(messages, ".*Authenticated as GitHub user.*") ~= nil
end

vim.wait(30000, function()
return has_passed()
end, 50)
]])

local result = child.lua([[
local auth_result = ""
local function has_passed()
auth_result = require("copilot.auth").is_authenticated() or ""
return auth_result == true
end

vim.wait(30000, function()
return has_passed()
end, 50)

return tostring(auth_result)
]])

u.expect_match(result, "true")
end

return T