fix(github-copilot): preserve legacy endpoint routing

This commit is contained in:
codgician 2026-07-28 13:51:48 +08:00
parent 58d0e1010b
commit 23f01440db
No known key found for this signature in database
2 changed files with 70 additions and 1 deletions

View file

@ -4,6 +4,8 @@ import time
from typing import Final
import httpx
from pydantic import TypeAdapter, ValidationError
from typing_extensions import TypedDict
from litellm._logging import verbose_logger
from litellm.llms.custom_httpx.http_handler import _get_httpx_client
@ -15,6 +17,17 @@ from .common_utils import (
get_copilot_auth_headers,
)
class _LegacyCopilotEndpoints(TypedDict, total=False):
api: str
class _LegacyCopilotTokenCache(TypedDict, total=False):
endpoints: _LegacyCopilotEndpoints
_LEGACY_COPILOT_TOKEN_CACHE_ADAPTER = TypeAdapter(_LegacyCopilotTokenCache)
# Constants (default values — overridable via environment variables at call time)
DEFAULT_GITHUB_CLIENT_ID: Final = "Iv1.b507a08c87ecfe98"
DEFAULT_GITHUB_DEVICE_CODE_URL: Final = "https://github.com/login/device/code"
@ -33,6 +46,14 @@ class Authenticator:
self.token_dir,
os.getenv("GITHUB_COPILOT_ACCESS_TOKEN_FILE", "access-token"),
)
self.legacy_api_key_file = os.path.join(
self.token_dir,
os.getenv("GITHUB_COPILOT_API_KEY_FILE", "api-key.json"),
)
if os.getenv("GITHUB_COPILOT_API_KEY_URL"):
verbose_logger.warning(
"GITHUB_COPILOT_API_KEY_URL is no longer used; LiteLLM sends the OAuth access token directly"
)
self._ensure_token_dir()
def get_access_token(self) -> str:
@ -82,7 +103,18 @@ class Authenticator:
)
def get_api_base(self) -> str | None:
return os.getenv("GITHUB_COPILOT_API_BASE")
configured_api_base = os.getenv("GITHUB_COPILOT_API_BASE")
if configured_api_base:
return configured_api_base
try:
with open(self.legacy_api_key_file, "r") as legacy_api_key_file:
legacy_cache = _LEGACY_COPILOT_TOKEN_CACHE_ADAPTER.validate_json(legacy_api_key_file.read())
except IOError:
return None
except ValidationError as e:
verbose_logger.warning(f"Error reading legacy GitHub Copilot API endpoint: {str(e)}")
return None
return legacy_cache.get("endpoints", {}).get("api")
def _ensure_token_dir(self) -> None:
"""Ensure the token directory exists."""

View file

@ -41,6 +41,7 @@ class TestGitHubCopilotAuthenticator:
auth = Authenticator()
assert auth.token_dir.endswith("/github_copilot")
assert auth.access_token_file.endswith("/access-token")
assert auth.legacy_api_key_file.endswith("/api-key.json")
mock_makedirs.assert_called_once()
def test_ensure_token_dir(self):
@ -52,6 +53,42 @@ class TestGitHubCopilotAuthenticator:
auth = Authenticator()
mock_makedirs.assert_called_once_with(auth.token_dir, exist_ok=True)
def test_get_api_base_prefers_environment(self, authenticator):
with (
patch.dict(
os.environ,
{"GITHUB_COPILOT_API_BASE": "https://configured.githubcopilot.example"},
clear=True,
),
patch("builtins.open", mock_open()) as mock_file,
):
assert authenticator.get_api_base() == "https://configured.githubcopilot.example"
mock_file.assert_not_called()
def test_get_api_base_uses_legacy_endpoint(self, authenticator):
legacy_cache = '{"token":"ignored","endpoints":{"api":"https://api.enterprise.githubcopilot.com"}}'
with (
patch.dict(os.environ, {}, clear=True),
patch("builtins.open", mock_open(read_data=legacy_cache)),
):
assert authenticator.get_api_base() == "https://api.enterprise.githubcopilot.com"
def test_deprecated_api_key_url_warns(self):
with (
patch.dict(
os.environ,
{"GITHUB_COPILOT_API_KEY_URL": "https://deprecated.example.com/token"},
clear=True,
),
patch("os.path.exists", return_value=True),
patch("litellm.llms.github_copilot.authenticator.verbose_logger.warning") as mock_warning,
):
Authenticator()
mock_warning.assert_called_once_with(
"GITHUB_COPILOT_API_KEY_URL is no longer used; LiteLLM sends the OAuth access token directly"
)
def test_get_github_headers(self, authenticator):
headers = authenticator._get_github_headers()
assert headers == {