Skip to content

Commit 274d6ed

Browse files
committed
chore(litellm): validate base URL, hide SSO button when invalid/has key, persist OAuth metadata, add expiry notice
1 parent fc5b73e commit 274d6ed

File tree

4 files changed

+46
-11
lines changed

4 files changed

+46
-11
lines changed

packages/types/src/provider-settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ const litellmSchema = baseProviderSettingsSchema.extend({
252252
litellmApiKey: z.string().optional(),
253253
litellmModelId: z.string().optional(),
254254
litellmUsePromptCache: z.boolean().optional(),
255+
// OAuth metadata (optional)
256+
litellmTokenType: z.string().optional(),
257+
litellmTokenExpiresAt: z.string().optional(),
258+
litellmTokenScope: z.string().optional(),
255259
})
256260

257261
const cerebrasSchema = apiModelIdProviderModelSchema.extend({

src/core/webview/ClineProvider.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,7 +1255,9 @@ export class ClineProvider
12551255
await this.upsertProviderProfile(currentApiConfigName, newConfiguration)
12561256
}
12571257

1258-
// LiteLLM
1258+
// LiteLLM OAuth2 SSO integration
1259+
// Flow: User clicks SSO button -> LiteLLM OAuth page -> redirect back with access_token
1260+
// Token is stored as API key for the LiteLLM provider. Based on LiteLLM PR #13227.
12591261
async handleLiteLLMCallback(oauthResponse: {
12601262
accessToken: string
12611263
tokenType: string
@@ -1277,14 +1279,34 @@ export class ClineProvider
12771279
apiProvider: "litellm",
12781280
litellmApiKey: accessToken,
12791281
litellmModelId: apiConfiguration?.litellmModelId || litellmDefaultModelId,
1282+
litellmTokenType: tokenType,
1283+
litellmTokenExpiresAt: expiresAt,
1284+
litellmTokenScope: scope,
12801285
}
12811286
await this.upsertProviderProfile(currentApiConfigName, newConfiguration)
12821287

12831288
// Notify webview of the configuration update
12841289
await this.postStateToWebview()
12851290

1286-
// Show success message to user
1287-
vscode.window.showInformationMessage("Successfully authenticated with LiteLLM via SSO!")
1291+
// Show success message to user including token expiry information
1292+
vscode.window.showInformationMessage(
1293+
`Successfully authenticated with LiteLLM via SSO! Token expires in ${expiresIn} seconds.`,
1294+
)
1295+
1296+
// Schedule a pre-expiry warning if expiry is reasonable
1297+
try {
1298+
const msUntilExpiry = expiresIn * 1000
1299+
const warnMs = Math.max(0, msUntilExpiry - 5 * 60 * 1000) // 5 minutes before
1300+
if (warnMs > 0 && warnMs < 7 * 24 * 60 * 60 * 1000) {
1301+
setTimeout(() => {
1302+
void vscode.window.showWarningMessage(
1303+
"Your LiteLLM OAuth token is about to expire. Please re-authenticate via SSO to avoid interruptions.",
1304+
)
1305+
}, warnMs)
1306+
}
1307+
} catch {
1308+
// ignore scheduling errors
1309+
}
12881310
}
12891311

12901312
// Glama

webview-ui/src/components/settings/providers/LiteLLM.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,16 @@ export const LiteLLM = ({
115115
{t("settings:providers.apiKeyStorageNotice")}
116116
</div>
117117

118-
{apiConfiguration?.litellmBaseUrl && (
119-
<VSCodeButtonLink
120-
href={getLiteLLMAuthUrl(apiConfiguration.litellmBaseUrl, uriScheme)}
121-
style={{ width: "100%" }}
122-
appearance="primary">
123-
{t("settings:providers.getLiteLLMApiKey")}
124-
</VSCodeButtonLink>
125-
)}
118+
{(() => {
119+
if (!apiConfiguration?.litellmBaseUrl || apiConfiguration?.litellmApiKey) return null
120+
const authUrl = getLiteLLMAuthUrl(apiConfiguration.litellmBaseUrl, uriScheme)
121+
if (!authUrl) return null
122+
return (
123+
<VSCodeButtonLink href={authUrl} style={{ width: "100%" }} appearance="primary">
124+
{t("settings:providers.getLiteLLMApiKey")}
125+
</VSCodeButtonLink>
126+
)
127+
})()}
126128

127129
<Button
128130
variant="outline"

webview-ui/src/oauth/urls.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export function getRequestyAuthUrl(uriScheme?: string) {
1717
}
1818

1919
export function getLiteLLMAuthUrl(baseUrl: string, uriScheme?: string) {
20+
// Validate URL format to avoid malformed links
21+
try {
22+
// Throws on invalid URL
23+
new URL(baseUrl)
24+
} catch {
25+
return ""
26+
}
2027
const cleanBaseUrl = baseUrl.replace(/\/+$/, "")
2128
return `${cleanBaseUrl}/sso/key/generate?response_type=oauth_token&redirect_uri=${getCallbackUrl("litellm", uriScheme)}`
2229
}

0 commit comments

Comments
 (0)