-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: add LiteLLM OAuth2 SSO authentication button #6612
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
Changes from all commits
26ef936
8c785e7
fc5b73e
274d6ed
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 | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,6 +30,7 @@ import { | |||||||||||||
| requestyDefaultModelId, | ||||||||||||||
| openRouterDefaultModelId, | ||||||||||||||
| glamaDefaultModelId, | ||||||||||||||
| litellmDefaultModelId, | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I notice |
||||||||||||||
| ORGANIZATION_ALLOW_ALL, | ||||||||||||||
| DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT, | ||||||||||||||
| DEFAULT_WRITE_DELAY_MS, | ||||||||||||||
|
|
@@ -1254,6 +1255,60 @@ export class ClineProvider | |||||||||||||
| await this.upsertProviderProfile(currentApiConfigName, newConfiguration) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // LiteLLM OAuth2 SSO integration | ||||||||||||||
| // Flow: User clicks SSO button -> LiteLLM OAuth page -> redirect back with access_token | ||||||||||||||
| // Token is stored as API key for the LiteLLM provider. Based on LiteLLM PR #13227. | ||||||||||||||
| async handleLiteLLMCallback(oauthResponse: { | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding inline documentation explaining the OAuth flow or linking to LiteLLM's OAuth documentation. This would help future maintainers understand the integration better. For example:
Suggested change
|
||||||||||||||
| accessToken: string | ||||||||||||||
| tokenType: string | ||||||||||||||
| expiresIn: number | ||||||||||||||
| scope?: string | ||||||||||||||
| }) { | ||||||||||||||
| let { apiConfiguration, currentApiConfigName } = await this.getState() | ||||||||||||||
|
|
||||||||||||||
| // Store the OAuth response metadata | ||||||||||||||
| const { accessToken, tokenType, expiresIn, scope } = oauthResponse | ||||||||||||||
| const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString() | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The token expiry time is calculated and logged but doesn't appear to be used elsewhere. Consider implementing token refresh logic or at least warning users when their token is about to expire. This would improve the user experience by preventing unexpected authentication failures. |
||||||||||||||
|
|
||||||||||||||
| this.log( | ||||||||||||||
| `LiteLLM OAuth success: ${tokenType} token expires in ${expiresIn}s (${expiresAt})${scope ? ` with scope: ${scope}` : ""}`, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| const newConfiguration: ProviderSettings = { | ||||||||||||||
| ...apiConfiguration, | ||||||||||||||
| apiProvider: "litellm", | ||||||||||||||
| litellmApiKey: accessToken, | ||||||||||||||
| litellmModelId: apiConfiguration?.litellmModelId || litellmDefaultModelId, | ||||||||||||||
| litellmTokenType: tokenType, | ||||||||||||||
| litellmTokenExpiresAt: expiresAt, | ||||||||||||||
| litellmTokenScope: scope, | ||||||||||||||
| } | ||||||||||||||
| await this.upsertProviderProfile(currentApiConfigName, newConfiguration) | ||||||||||||||
|
|
||||||||||||||
| // Notify webview of the configuration update | ||||||||||||||
| await this.postStateToWebview() | ||||||||||||||
|
|
||||||||||||||
| // Show success message to user including token expiry information | ||||||||||||||
| vscode.window.showInformationMessage( | ||||||||||||||
| `Successfully authenticated with LiteLLM via SSO! Token expires in ${expiresIn} seconds.`, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // Schedule a pre-expiry warning if expiry is reasonable | ||||||||||||||
| try { | ||||||||||||||
| const msUntilExpiry = expiresIn * 1000 | ||||||||||||||
| const warnMs = Math.max(0, msUntilExpiry - 5 * 60 * 1000) // 5 minutes before | ||||||||||||||
| if (warnMs > 0 && warnMs < 7 * 24 * 60 * 60 * 1000) { | ||||||||||||||
| setTimeout(() => { | ||||||||||||||
| void vscode.window.showWarningMessage( | ||||||||||||||
| "Your LiteLLM OAuth token is about to expire. Please re-authenticate via SSO to avoid interruptions.", | ||||||||||||||
| ) | ||||||||||||||
| }, warnMs) | ||||||||||||||
| } | ||||||||||||||
| } catch { | ||||||||||||||
| // ignore scheduling errors | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Glama | ||||||||||||||
|
|
||||||||||||||
| async handleGlamaCallback(code: string) { | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,3 +15,15 @@ export function getOpenRouterAuthUrl(uriScheme?: string) { | |||||||||||||||||||||
| export function getRequestyAuthUrl(uriScheme?: string) { | ||||||||||||||||||||||
| return `https://app.requesty.ai/oauth/authorize?callback_url=${getCallbackUrl("requesty", uriScheme)}` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export function getLiteLLMAuthUrl(baseUrl: string, uriScheme?: string) { | ||||||||||||||||||||||
| // Validate URL format to avoid malformed links | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| // Throws on invalid URL | ||||||||||||||||||||||
| new URL(baseUrl) | ||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||
| return "" | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const cleanBaseUrl = baseUrl.replace(/\/+$/, "") | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding URL validation before constructing the OAuth URL. Something like:
Suggested change
This would prevent malformed URLs from causing issues downstream. |
||||||||||||||||||||||
| return `${cleanBaseUrl}/sso/key/generate?response_type=oauth_token&redirect_uri=${getCallbackUrl("litellm", uriScheme)}` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
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.
Is 86400 seconds (24 hours) a reasonable default for token expiry? Some OAuth tokens might have much shorter lifespans. Could we consider a more conservative default or make this configurable?