-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Feature/GitHub copilot integration to lite llm #675
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
base: main
Are you sure you want to change the base?
Changes from 8 commits
b7236e5
2f668d8
3f1ca78
47e86f8
55e10f4
6dd245b
1fbc220
76f669d
6b9e4d2
985e560
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -62,6 +62,18 @@ class LLMConfig(BaseModel): | |||||||||||||||||||||||||
| api_base: str | None = None | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def _is_github_copilot_authenticated() -> bool: | ||||||||||||||||||||||||||
| """Check if GitHub Copilot has a valid OAuth token on disk. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| LiteLLM stores the token at ~/.config/litellm/github_copilot/access-token. | ||||||||||||||||||||||||||
| If the file is missing, any LLM call would trigger the device-code OAuth | ||||||||||||||||||||||||||
| flow (requiring manual browser intervention), so we fail fast instead. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||
| token_file = Path.home() / ".config" / "litellm" / "github_copilot" / "access-token" | ||||||||||||||||||||||||||
| return token_file.exists() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def _normalize_api_base(provider: str, api_base: str | None) -> str | None: | ||||||||||||||||||||||||||
| """Normalize api_base for LiteLLM provider-specific expectations. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -257,6 +269,7 @@ def get_model_name(config: LLMConfig) -> str: | |||||||||||||||||||||||||
| "gemini": "gemini/", | ||||||||||||||||||||||||||
| "deepseek": "deepseek/", | ||||||||||||||||||||||||||
| "ollama": "ollama/", | ||||||||||||||||||||||||||
| "github_copilot": "github_copilot/", | ||||||||||||||||||||||||||
cubic-dev-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| prefix = provider_prefixes.get(config.provider, "") | ||||||||||||||||||||||||||
|
|
@@ -313,30 +326,44 @@ async def check_llm_health( | |||||||||||||||||||||||||
| if config is None: | ||||||||||||||||||||||||||
| config = get_llm_config() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Check if API key is configured (except for Ollama) | ||||||||||||||||||||||||||
| if config.provider != "ollama" and not config.api_key: | ||||||||||||||||||||||||||
| # Check if API key is configured (except for Ollama and GitHub Copilot which use OAuth) | ||||||||||||||||||||||||||
| if config.provider not in ("ollama", "github_copilot") and not config.api_key: | ||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| "healthy": False, | ||||||||||||||||||||||||||
| "provider": config.provider, | ||||||||||||||||||||||||||
| "model": config.model, | ||||||||||||||||||||||||||
| "error_code": "api_key_missing", | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # GitHub Copilot: fail fast if not authenticated to avoid triggering device flow | ||||||||||||||||||||||||||
| if config.provider == "github_copilot" and not _is_github_copilot_authenticated(): | ||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| "healthy": False, | ||||||||||||||||||||||||||
| "provider": config.provider, | ||||||||||||||||||||||||||
| "model": config.model, | ||||||||||||||||||||||||||
| "error_code": "github_copilot_not_authenticated", | ||||||||||||||||||||||||||
| "message": "GitHub Copilot is not authenticated. Please authenticate via Settings first.", | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| model_name = get_model_name(config) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| prompt = test_prompt or "Hi" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| # Make a minimal test call with timeout | ||||||||||||||||||||||||||
| # Pass API key directly to avoid race conditions with global os.environ | ||||||||||||||||||||||||||
| # For GitHub Copilot, LiteLLM manages OAuth tokens internally, so don't pass api_key | ||||||||||||||||||||||||||
| kwargs: dict[str, Any] = { | ||||||||||||||||||||||||||
| "model": model_name, | ||||||||||||||||||||||||||
| "messages": [{"role": "user", "content": prompt}], | ||||||||||||||||||||||||||
| "max_tokens": 16, | ||||||||||||||||||||||||||
| "api_key": config.api_key, | ||||||||||||||||||||||||||
| "api_base": _normalize_api_base(config.provider, config.api_base), | ||||||||||||||||||||||||||
| "timeout": LLM_TIMEOUT_HEALTH_CHECK, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Only pass api_key for providers that use it (not OAuth-based providers) | ||||||||||||||||||||||||||
| if config.provider != "github_copilot": | ||||||||||||||||||||||||||
| kwargs["api_key"] = config.api_key | ||||||||||||||||||||||||||
| kwargs["api_base"] = _normalize_api_base(config.provider, config.api_base) | ||||||||||||||||||||||||||
|
Comment on lines
+379
to
+382
|
||||||||||||||||||||||||||
| reasoning_effort = _get_reasoning_effort(config.provider, model_name) | ||||||||||||||||||||||||||
| if reasoning_effort: | ||||||||||||||||||||||||||
| kwargs["reasoning_effort"] = reasoning_effort | ||||||||||||||||||||||||||
|
|
@@ -414,21 +441,32 @@ async def complete( | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| model_name = get_model_name(config) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # GitHub Copilot: fail fast if not authenticated to avoid triggering device flow | ||||||||||||||||||||||||||
| if config.provider == "github_copilot" and not _is_github_copilot_authenticated(): | ||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||
| "GitHub Copilot is not authenticated. " | ||||||||||||||||||||||||||
| "Please authenticate via Settings before using AI features." | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| messages = [] | ||||||||||||||||||||||||||
| if system_prompt: | ||||||||||||||||||||||||||
| messages.append({"role": "system", "content": system_prompt}) | ||||||||||||||||||||||||||
| messages.append({"role": "user", "content": prompt}) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||
| # Pass API key directly to avoid race conditions with global os.environ | ||||||||||||||||||||||||||
| # For GitHub Copilot, LiteLLM manages OAuth tokens internally, so don't pass api_key | ||||||||||||||||||||||||||
| kwargs: dict[str, Any] = { | ||||||||||||||||||||||||||
| "model": model_name, | ||||||||||||||||||||||||||
| "messages": messages, | ||||||||||||||||||||||||||
| "max_tokens": max_tokens, | ||||||||||||||||||||||||||
| "api_key": config.api_key, | ||||||||||||||||||||||||||
| "api_base": _normalize_api_base(config.provider, config.api_base), | ||||||||||||||||||||||||||
| "timeout": LLM_TIMEOUT_COMPLETION, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Only pass api_key for providers that use it (not OAuth-based providers) | ||||||||||||||||||||||||||
| if config.provider != "github_copilot": | ||||||||||||||||||||||||||
| kwargs["api_key"] = config.api_key | ||||||||||||||||||||||||||
| kwargs["api_base"] = _normalize_api_base(config.provider, config.api_base) | ||||||||||||||||||||||||||
|
Comment on lines
+481
to
+485
|
||||||||||||||||||||||||||
| # Only pass api_key for providers that use it (not OAuth-based providers) | |
| if config.provider != "github_copilot": | |
| kwargs["api_key"] = config.api_key | |
| kwargs["api_base"] = _normalize_api_base(config.provider, config.api_base) | |
| # Only pass api_key/api_base for providers that use them (not OAuth-based providers) | |
| if config.provider != "github_copilot": | |
| kwargs["api_key"] = config.api_key | |
| normalized_api_base = _normalize_api_base(config.provider, config.api_base) | |
| if normalized_api_base is not None: | |
| kwargs["api_base"] = normalized_api_base |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as in other functions - api_base should not be passed to LiteLLM for GitHub Copilot. The code should skip setting api_base for github_copilot provider.
| kwargs["api_key"] = config.api_key | |
| kwargs["api_key"] = config.api_key | |
| # Only pass api_base for providers that support it (not GitHub Copilot) | |
| if config.provider != "github_copilot": |
Uh oh!
There was an error while loading. Please reload this page.