Feature/GitHub copilot integration to lite llm#675
Feature/GitHub copilot integration to lite llm#675dawgdevv wants to merge 10 commits intosrbhr:mainfrom
Conversation
…ration and terminal instructions
…ates and improving UI feedback
… and improved UI feedback
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive GitHub Copilot OAuth authentication support to Resume Matcher, enabling users to leverage GitHub Copilot's AI capabilities without manual API key management. The implementation introduces a device-flow OAuth system with backend token management and a frontend UI for authentication state management.
Changes:
- Added GitHub Copilot as a new LLM provider with OAuth device-flow authentication
- Implemented backend endpoints for initiating, cancelling, and checking GitHub Copilot authentication status
- Created frontend UI components in Settings page for OAuth authentication workflow with status indicators
- Updated health check logic to skip OAuth providers and avoid triggering unwanted authentication flows
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 22 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/frontend/package-lock.json | Version bump to 1.1.0 and extensive peer dependency flag changes (appears unrelated to feature) |
| apps/frontend/lib/api/config.ts | Added GitHub Copilot provider type and OAuth-specific API functions (initiate, cancel, check status, logout) |
| apps/frontend/hooks/use-regenerate-wizard.ts | Added pre-flight LLM health check before regeneration to catch authentication issues |
| apps/frontend/hooks/use-enrichment-wizard.ts | Added pre-flight LLM health check before enrichment analysis |
| apps/frontend/components/builder/resume-builder.tsx | Added pre-flight LLM health checks before cover letter and outreach generation |
| apps/frontend/app/(default)/tailor/page.tsx | Added pre-flight LLM health check before resume tailoring |
| apps/frontend/app/(default)/settings/page.tsx | Added GitHub Copilot authentication panel with status management, polling, and OAuth flow UI |
| apps/backend/app/routers/health.py | Modified health check to use token file inspection for GitHub Copilot instead of triggering OAuth flow |
| apps/backend/app/routers/config.py | Added four new endpoints for GitHub Copilot OAuth management and logout functionality |
| apps/backend/app/llm.py | Added GitHub Copilot authentication checks and modified LLM call functions to skip api_key for OAuth providers |
| apps/backend/app/config.py | Added github_copilot to provider type literals and provider mapping |
Files not reviewed (1)
- apps/frontend/package-lock.json: Language not supported
|
|
||
| # 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) |
There was a problem hiding this comment.
The api_base parameter should not be passed to LiteLLM for GitHub Copilot provider, as GitHub Copilot uses OAuth and has a fixed API endpoint managed by LiteLLM. Currently, this code only skips api_key for github_copilot, but api_base is still being set on line 436 when it's provided in the config. This could cause issues if a user sets a custom API base for GitHub Copilot. The condition on line 467 should also check if api_base should be passed, similar to how api_key is handled.
| # 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 |
| const healthCheck = await testLlmConnection(); | ||
|
|
||
| if (!healthCheck.healthy) { | ||
| const errorMsg = healthCheck.error_code === 'not_authenticated' |
There was a problem hiding this comment.
Same issue as in the hooks - the error code check should include 'github_copilot_not_authenticated' in addition to 'not_authenticated'.
| const errorMsg = healthCheck.error_code === 'not_authenticated' | |
| const isAuthError = | |
| healthCheck.error_code === 'not_authenticated' || | |
| healthCheck.error_code === 'github_copilot_not_authenticated'; | |
| const errorMsg = isAuthError |
| const errorMsg = healthCheck.error_code === 'not_authenticated' | ||
| ? 'AI provider not authenticated. Please authenticate in Settings before tailoring resumes.' | ||
| : healthCheck.error || 'AI provider connection failed. Please check your settings.'; |
There was a problem hiding this comment.
Same issue as in other places - the error code check should include 'github_copilot_not_authenticated' in addition to 'not_authenticated'.
| const errorMsg = healthCheck.error_code === 'not_authenticated' | |
| ? 'AI provider not authenticated. Please authenticate in Settings before tailoring resumes.' | |
| : healthCheck.error || 'AI provider connection failed. Please check your settings.'; | |
| const errorMsg = | |
| healthCheck.error_code === 'not_authenticated' || | |
| healthCheck.error_code === 'github_copilot_not_authenticated' | |
| ? 'AI provider not authenticated. Please authenticate in Settings before tailoring resumes.' | |
| : healthCheck.error || 'AI provider connection failed. Please check your settings.'; |
| } | ||
|
|
||
|
|
||
| # Module-level reference to the running auth task so it can be cancelled. |
There was a problem hiding this comment.
Using a module-level global variable _copilot_auth_task to track authentication state creates a potential concurrency issue in a multi-user environment. If multiple users try to authenticate GitHub Copilot simultaneously, they'll all share the same task reference, which could lead to one user cancelling another user's authentication flow. Consider using a user-specific session identifier or request context to isolate authentication tasks per user. Alternatively, document that this implementation assumes single-user deployment only.
| # Module-level reference to the running auth task so it can be cancelled. | |
| # Module-level reference to the running auth task so it can be cancelled. | |
| # NOTE: This implementation assumes a single-user or single-admin deployment | |
| # for GitHub Copilot authentication. Only one authentication flow is expected | |
| # to be active at a time for the entire backend process. In a multi-user | |
| # environment, concurrent calls to the GitHub Copilot auth endpoints could | |
| # interfere with each other because they share this task reference. For | |
| # multi-user deployments, replace this with per-user or per-session tracking | |
| # (for example, keyed by a session identifier) instead of a single global. |
| @@ -116,6 +124,9 @@ export default function SettingsPage() { | |||
| // Health check result from manual test | |||
| const [healthCheck, setHealthCheck] = useState<LLMHealthCheck | null>(null); | |||
|
|
|||
| // Abort controller for cancelling test connection | |||
| const testConnectionAbortRef = useRef<AbortController | null>(null); | |||
|
|
|||
| // Feature config state | |||
| const [enableCoverLetter, setEnableCoverLetter] = useState(false); | |||
| const [enableOutreach, setEnableOutreach] = useState(false); | |||
| @@ -286,8 +297,68 @@ export default function SettingsPage() { | |||
| }; | |||
| }, [t]); | |||
|
|
|||
| // Check GitHub Copilot auth status when provider changes or on mount | |||
| useEffect(() => { | |||
| let cancelled = false; | |||
| let interval: NodeJS.Timeout | null = null; | |||
|
|
|||
| async function checkAuthStatus() { | |||
| if (provider === 'github_copilot' && !cancelled) { | |||
| try { | |||
| const authStatus = await checkGithubCopilotStatus(); | |||
| if (!cancelled) { | |||
| if (authStatus.authenticated) { | |||
| setCopilotAuthStatus('authenticated'); | |||
| } else { | |||
| // Only update if not currently initiating (to keep UI showing "Initiating...") | |||
| setCopilotAuthStatus(prev => prev === 'initiating' ? prev : 'not_authenticated'); | |||
| } | |||
| } | |||
| } catch { | |||
| if (!cancelled) { | |||
| // Only update if not currently initiating | |||
| setCopilotAuthStatus(prev => prev === 'initiating' ? prev : 'unknown'); | |||
| } | |||
| } | |||
| } | |||
| } | |||
|
|
|||
| // Check immediately on mount/provider change | |||
| checkAuthStatus(); | |||
|
|
|||
| // Only poll when authentication is in progress (initiating) | |||
| // Stop polling once authenticated to save resources | |||
| if (provider === 'github_copilot' && copilotAuthStatus === 'initiating') { | |||
| interval = setInterval(checkAuthStatus, 5000); | |||
| } | |||
|
|
|||
| return () => { | |||
| cancelled = true; | |||
| if (interval) { | |||
| clearInterval(interval); | |||
| interval = null; | |||
| } | |||
| }; | |||
| }, [provider, copilotAuthStatus]); | |||
There was a problem hiding this comment.
The PR description claims "authentication state persists across page navigation" and "State persists via token file on disk", but the frontend doesn't persist the copilotAuthStatus state in localStorage or any other client-side storage. The status is fetched from the backend on mount via the token file check, but if a user is in the middle of authentication ('initiating' state) and navigates away, that UI state is lost. When they return, the status will show 'not_authenticated' or 'unknown' instead of 'initiating', which could be confusing if they're still waiting for the backend auth flow to complete.
| useEffect(() => { | ||
| let cancelled = false; | ||
| let interval: NodeJS.Timeout | null = null; | ||
|
|
||
| async function checkAuthStatus() { | ||
| if (provider === 'github_copilot' && !cancelled) { | ||
| try { | ||
| const authStatus = await checkGithubCopilotStatus(); | ||
| if (!cancelled) { | ||
| if (authStatus.authenticated) { | ||
| setCopilotAuthStatus('authenticated'); | ||
| } else { | ||
| // Only update if not currently initiating (to keep UI showing "Initiating...") | ||
| setCopilotAuthStatus(prev => prev === 'initiating' ? prev : 'not_authenticated'); | ||
| } | ||
| } | ||
| } catch { | ||
| if (!cancelled) { | ||
| // Only update if not currently initiating | ||
| setCopilotAuthStatus(prev => prev === 'initiating' ? prev : 'unknown'); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Check immediately on mount/provider change | ||
| checkAuthStatus(); | ||
|
|
||
| // Only poll when authentication is in progress (initiating) | ||
| // Stop polling once authenticated to save resources | ||
| if (provider === 'github_copilot' && copilotAuthStatus === 'initiating') { | ||
| interval = setInterval(checkAuthStatus, 5000); | ||
| } | ||
|
|
||
| return () => { | ||
| cancelled = true; | ||
| if (interval) { | ||
| clearInterval(interval); | ||
| interval = null; | ||
| } | ||
| }; | ||
| }, [provider, copilotAuthStatus]); |
There was a problem hiding this comment.
The polling interval (5 seconds) that runs indefinitely while copilotAuthStatus === 'initiating' could continue even if the user navigates away and comes back, potentially creating multiple overlapping intervals. While there is cleanup on unmount (lines 335-341), if the user navigates away and the status remains 'initiating', returning to the page will start a new interval without stopping the old one. Consider adding a ref to track the interval ID and clearing it before creating a new one, or using a single global interval managed by the effect.
| import asyncio | ||
| from pathlib import Path | ||
| import logging |
| except Exception: | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| except Exception: | |
| pass | |
| except Exception as exc: | |
| logging.getLogger(__name__).warning( | |
| "Failed to check GitHub Copilot authentication status: %s", | |
| exc, | |
| ) |
| # Give the task a moment to acknowledge cancellation | ||
| await _asyncio.wait_for(_asyncio.shield(_copilot_auth_task), timeout=2.0) | ||
| except (_asyncio.CancelledError, _asyncio.TimeoutError, Exception): | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| pass | |
| # Failures while waiting for cancellation are non-fatal; log and continue. | |
| logging.getLogger(__name__).debug( | |
| "Ignoring exception while waiting for Copilot auth task cancellation", | |
| exc_info=True, | |
| ) |
There was a problem hiding this comment.
7 issues found across 11 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/backend/app/routers/config.py">
<violation number="1" location="apps/backend/app/routers/config.py:525">
P2: Rule violated: **Flag Security Vulnerabilities**
Raw exception details are exposed to the client via `str(e)`, which can leak sensitive server information such as filesystem paths (including the OS username from the home directory), permission details, or other internal errors. Return a generic error message to the client and log the full exception server-side instead.
This violates the "exposed data" clause of the security rule.</violation>
<violation number="2" location="apps/backend/app/routers/config.py:681">
P2: Rule violated: **Flag Security Vulnerabilities**
Raw exception message is returned to the client via `str(e)` in the error response. Internal exception details from `litellm` or other modules could expose sensitive implementation details, internal URLs, or file paths. Log the full exception server-side (already done) but return only a generic message to the client.</violation>
</file>
<file name="apps/backend/app/llm.py">
<violation number="1" location="apps/backend/app/llm.py:73">
P2: Hardcoded Copilot token path ignores LiteLLM’s configurable token directory/file settings (e.g., GITHUB_COPILOT_TOKEN_DIR/GITHUB_COPILOT_ACCESS_TOKEN_FILE), which can falsely report unauthenticated on non-default paths.</violation>
<violation number="2" location="apps/backend/app/llm.py:272">
P2: `get_model_name` now supports GitHub Copilot prefixes, but `known_prefixes` does not include `github_copilot/`, so already‑prefixed Copilot model names will be double‑prefixed (e.g., `github_copilot/github_copilot/gpt-4`).</violation>
</file>
<file name="apps/backend/app/routers/health.py">
<violation number="1" location="apps/backend/app/routers/health.py:78">
P2: llm_configured is always true for github_copilot even when no token/authentication exists, which can mislead setup gating that relies on llm_configured.</violation>
</file>
<file name="apps/frontend/app/(default)/settings/page.tsx">
<violation number="1" location="apps/frontend/app/(default)/settings/page.tsx:320">
P2: Auth status polling keeps the UI in "initiating" on errors, which can leave users stuck in an infinite "Initiating..." state with no ability to retry if the backend status check fails.</violation>
<violation number="2" location="apps/frontend/app/(default)/settings/page.tsx:942">
P2: New Copilot UI text is hardcoded instead of using the localization helper, which breaks the app’s i18n pattern for this section.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…pport, improved error handling, and localization
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/backend/app/llm.py">
<violation number="1" location="apps/backend/app/llm.py:82">
P2: Environment-variable token paths are passed directly to Path without expanding `~`, which can cause false negatives for users who set `GITHUB_COPILOT_ACCESS_TOKEN_FILE` or `GITHUB_COPILOT_TOKEN_DIR` with a home shortcut. Use `expanduser()` when building these paths.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…aths to home directory
|
Hi @dawgdevv Before implementing these large feature files, can we have a discussion about whether we want this or not? I know that LiteLLM has a lot of features, and we could have used your help in fixing some issues with docker. But this isn't the feature that I want to add right now, not at least by discussing with the community. |
okay understood just so i added this feature for my personal use thought might make a pr of this sorry for not asking you Reasoning: I added GitHub Copilot integration to remove the dependency on external API keys. Most students already have access to Copilot through the GitHub Education Pack or free tier, but not everyone has API credits or wants to manage billing. By leveraging a tool they already use, Resume Matcher becomes instantly accessible, lowering friction and increasing adoption among students. |
|
@srbhr i am open to working on other issues in this repo |
|
I understand @dawgdevv LiteLLM allows signing in with Claude subscriptions too! But nothing is official from Anthropic for how to use the signups here. They're intended for using the native platform, your account may get blocked/banned for doing so. I think, that was the case with Open Code where Anthropic didn't like people signing in to OpenCode with their Claude code subscriptions. |
|
Although, if you want to preserve your features. I can merge this onto a different branch for folks to try it out. 😉 Let me know. |
I think Copilot allows this. I’ve checked the important parts on our side. We can ship it as an experimental feature and collect feedback from users to see how it performs in real use. Different branch is a good idea thank you |
Pull Request Title
Add GitHub Copilot OAuth Authentication Support to Resume Matcher
Related Issue
N/A - This is a new feature implementation
Description
This PR adds comprehensive GitHub Copilot OAuth authentication support to Resume Matcher, allowing users to leverage GitHub Copilot's AI capabilities for resume improvement without requiring manual API key management. The implementation includes a complete device-flow OAuth authentication system with proper state management, cancellation support, and user-friendly UI feedback.
copilot:summary
Type
Proposed Changes
Backend Implementation
1. Health Check Endpoint (
apps/backend/app/routers/health.py):_check_github_copilot_status()to check token file without triggering authentication2. Configuration Endpoints (
apps/backend/app/routers/config.py):initiate_github_copilot_auth()endpoint to start OAuth device flowcancel_github_copilot_auth()endpoint to abort ongoing authentication_log_llm_health_check()to skip OAuth providers during background checks/github-copilot/auth-statusendpoint to check authentication state3. LLM Integration (
apps/backend/app/llm.py):_is_github_copilot_authenticated()helper to verify token existencecheck_llm_health()to return proper error codes for unauthenticated CopilotFrontend Implementation
1. API Client (
apps/frontend/lib/api/config.ts):initiateGithubCopilotAuth()function to start authentication flowcancelGithubCopilotAuth()function to abort authenticationcheckGithubCopilotStatus()function to verify authentication statetestLlmConnection()with AbortSignal support for cancellation2. Settings Page (
apps/frontend/app/(default)/settings/page.tsx):Screenshots / Code Snippets (if applicable)
Authentication Flow UI States:
1. Initial State (Not Authenticated):
2. During Authentication (Initiating):
3. Authenticated State:
How to Test
Prerequisites:
Test Scenarios:
1. Complete Authentication Flow:
2. Cancel Authentication:
3. Logout Flow:
~/.config/litellm/github_copilot/4. State Persistence:
5. Error Handling:
Checklist
Additional Information
Architecture Overview:
OAuth Device Flow Implementation:
~/.config/litellm/github_copilot/access-tokenState Management:
copilotAuthStatus: 'unknown' | 'initiating' | 'not_authenticated' | 'authenticated'Security Considerations:
Note - Contact me for the further clarification and also suggest if any thing needs to be tweaked or changed
Video Demo:
PS: Tailor your resume is also working just i don't wanted to re-record
2026-02-13.17-05-30_2x.mp4
Summary by cubic
Added GitHub Copilot as an LLM provider via LiteLLM with OAuth device flow, plus a Settings UI to start/check/cancel auth and logout. Health checks and guards prevent triggering device flow and block AI actions until authenticated; token path overrides now support env vars and home (~) expansion, with localized UI text.
New Features
Migration
Written for commit 985e560. Summary will update on new commits.