Skip to content

Commit 0f5d09e

Browse files
authored
Fix API key management and remove Groq provider (microsoft#2559)
* Fix google and anthropic key management. Also remove groq * Fix model providers getting stuck with bad keys
1 parent cb836ee commit 0f5d09e

File tree

7 files changed

+79
-77
lines changed

7 files changed

+79
-77
lines changed

src/extension/byok/common/byokProvider.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,67 @@ export function isBYOKEnabled(copilotToken: Omit<CopilotToken, 'token'>, capiCli
191191
const byokAllowed = (copilotToken.isInternal || copilotToken.isIndividual) && !isGHE;
192192
return byokAllowed;
193193
}
194+
195+
/**
196+
* Result of handling an API key update operation.
197+
*/
198+
export interface HandleAPIKeyUpdateResult {
199+
/**
200+
* The new API key value, or undefined if the key was deleted or operation was cancelled.
201+
*/
202+
apiKey: string | undefined;
203+
/**
204+
* Whether the API key was deleted (user entered empty string during reconfigure).
205+
*/
206+
deleted: boolean;
207+
/**
208+
* Whether the operation was cancelled (user dismissed the input).
209+
*/
210+
cancelled: boolean;
211+
}
212+
213+
/**
214+
* Storage service interface for BYOK API key operations.
215+
* This is a minimal interface to avoid importing the full IBYOKStorageService in common code.
216+
*/
217+
export interface IBYOKStorageServiceLike {
218+
getAPIKey(providerName: string, modelId?: string): Promise<string | undefined>;
219+
storeAPIKey(providerName: string, apiKey: string, authType: BYOKAuthType, modelId?: string): Promise<void>;
220+
deleteAPIKey(providerName: string, authType: BYOKAuthType, modelId?: string): Promise<void>;
221+
}
222+
223+
/**
224+
* Handles API key update flow for BYOK providers using a consistent pattern.
225+
* This utility handles all three cases from promptForAPIKey:
226+
* - undefined: user cancelled/dismissed the input
227+
* - empty string: user wants to delete the saved key (only when reconfiguring)
228+
* - non-empty string: user provided a new API key
229+
*
230+
* @param providerName - Name of the provider (e.g., 'Anthropic', 'Gemini')
231+
* @param storageService - Storage service for API key operations
232+
* @param promptForAPIKeyFn - Function to prompt user for API key
233+
* @returns Result containing the new API key (if any) and status flags
234+
*/
235+
export async function handleAPIKeyUpdate(
236+
providerName: string,
237+
storageService: IBYOKStorageServiceLike,
238+
promptForAPIKeyFn: (providerName: string, reconfigure: boolean) => Promise<string | undefined>
239+
): Promise<HandleAPIKeyUpdateResult> {
240+
const existingKey = await storageService.getAPIKey(providerName);
241+
const isReconfiguring = existingKey !== undefined;
242+
243+
const newAPIKey = await promptForAPIKeyFn(providerName, isReconfiguring);
244+
245+
if (newAPIKey === undefined) {
246+
// User cancelled/dismissed the input
247+
return { apiKey: undefined, deleted: false, cancelled: true };
248+
} else if (newAPIKey === '') {
249+
// User wants to delete the key (only valid when reconfiguring)
250+
await storageService.deleteAPIKey(providerName, BYOKAuthType.GlobalApiKey);
251+
return { apiKey: undefined, deleted: true, cancelled: false };
252+
} else {
253+
// User provided a new API key
254+
await storageService.storeAPIKey(providerName, newAPIKey, BYOKAuthType.GlobalApiKey);
255+
return { apiKey: newAPIKey, deleted: false, cancelled: false };
256+
}
257+
}

src/extension/byok/vscode-node/anthropicProvider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { toErrorMessage } from '../../../util/common/errorMessage';
1717
import { RecordedProgress } from '../../../util/common/progressRecorder';
1818
import { generateUuid } from '../../../util/vs/base/common/uuid';
1919
import { anthropicMessagesToRawMessagesForLogging, apiMessageToAnthropicMessage } from '../common/anthropicMessageConverter';
20-
import { BYOKAuthType, BYOKKnownModels, byokKnownModelsToAPIInfo, BYOKModelCapabilities, BYOKModelProvider, LMResponsePart } from '../common/byokProvider';
20+
import { BYOKAuthType, BYOKKnownModels, byokKnownModelsToAPIInfo, BYOKModelCapabilities, BYOKModelProvider, handleAPIKeyUpdate, LMResponsePart } from '../common/byokProvider';
2121
import { IBYOKStorageService } from './byokStorageService';
2222
import { promptForAPIKey } from './byokUIService';
2323

@@ -100,9 +100,9 @@ export class AnthropicLMProvider implements BYOKModelProvider<LanguageModelChatI
100100
}
101101

102102
async updateAPIKey(): Promise<void> {
103-
this._apiKey = await promptForAPIKey(AnthropicLMProvider.providerName, await this._byokStorageService.getAPIKey(AnthropicLMProvider.providerName) !== undefined);
104-
if (this._apiKey) {
105-
await this._byokStorageService.storeAPIKey(AnthropicLMProvider.providerName, this._apiKey, BYOKAuthType.GlobalApiKey);
103+
const result = await handleAPIKeyUpdate(AnthropicLMProvider.providerName, this._byokStorageService, promptForAPIKey);
104+
if (!result.cancelled) {
105+
this._apiKey = result.apiKey;
106106
this._anthropicAPIClient = undefined;
107107
}
108108
}

src/extension/byok/vscode-node/baseOpenAICompatibleProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ export abstract class BaseOpenAICompatibleLMProvider implements BYOKModelProvide
8181
}
8282
}
8383
} catch (e) {
84+
// Likely bad API key so we will prompt user to update it one more time
85+
if (!options.silent && e instanceof Error && e.message.includes('key')) {
86+
await this.updateAPIKey();
87+
// Silent as to not prompt the user again
88+
return this.provideLanguageModelChatInformation({ silent: true }, token);
89+
}
8490
this._logService.error(e, `Error fetching available ${this._name} models`);
8591
return [];
8692
}

src/extension/byok/vscode-node/byokContribution.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { BYOKStorageService, IBYOKStorageService } from './byokStorageService';
1919
import { CustomOAIModelConfigurator } from './customOAIModelConfigurator';
2020
import { CustomOAIBYOKModelProvider } from './customOAIProvider';
2121
import { GeminiNativeBYOKLMProvider } from './geminiNativeProvider';
22-
import { GroqBYOKLMProvider } from './groqProvider';
2322
import { OllamaLMProvider } from './ollamaProvider';
2423
import { OAIBYOKLMProvider } from './openAIProvider';
2524
import { OpenRouterLMProvider } from './openRouterProvider';
@@ -88,7 +87,6 @@ export class BYOKContrib extends Disposable implements IExtensionContribution {
8887
const knownModels = await this.fetchKnownModelList(this._fetcherService);
8988
this._providers.set(OllamaLMProvider.providerName.toLowerCase(), instantiationService.createInstance(OllamaLMProvider, this._configurationService.getConfig(ConfigKey.OllamaEndpoint), this._byokStorageService));
9089
this._providers.set(AnthropicLMProvider.providerName.toLowerCase(), instantiationService.createInstance(AnthropicLMProvider, knownModels[AnthropicLMProvider.providerName], this._byokStorageService));
91-
this._providers.set(GroqBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(GroqBYOKLMProvider, knownModels[GroqBYOKLMProvider.providerName], this._byokStorageService));
9290
this._providers.set(GeminiNativeBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(GeminiNativeBYOKLMProvider, knownModels[GeminiNativeBYOKLMProvider.providerName], this._byokStorageService));
9391
this._providers.set(XAIBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(XAIBYOKLMProvider, knownModels[XAIBYOKLMProvider.providerName], this._byokStorageService));
9492
this._providers.set(OAIBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(OAIBYOKLMProvider, knownModels[OAIBYOKLMProvider.providerName], this._byokStorageService));

src/extension/byok/vscode-node/geminiNativeProvider.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IRequestLogger } from '../../../platform/requestLogger/node/requestLogg
1313
import { toErrorMessage } from '../../../util/common/errorMessage';
1414
import { RecordedProgress } from '../../../util/common/progressRecorder';
1515
import { generateUuid } from '../../../util/vs/base/common/uuid';
16-
import { BYOKAuthType, BYOKKnownModels, byokKnownModelsToAPIInfo, BYOKModelCapabilities, BYOKModelProvider, LMResponsePart } from '../common/byokProvider';
16+
import { BYOKAuthType, BYOKKnownModels, byokKnownModelsToAPIInfo, BYOKModelCapabilities, BYOKModelProvider, handleAPIKeyUpdate, LMResponsePart } from '../common/byokProvider';
1717
import { toGeminiFunction as toGeminiFunctionDeclaration, ToolJsonSchema } from '../common/geminiFunctionDeclarationConverter';
1818
import { apiMessageToGeminiMessage, geminiMessagesToRawMessagesForLogging } from '../common/geminiMessageConverter';
1919
import { IBYOKStorageService } from './byokStorageService';
@@ -59,9 +59,10 @@ export class GeminiNativeBYOKLMProvider implements BYOKModelProvider<LanguageMod
5959
}
6060

6161
async updateAPIKey(): Promise<void> {
62-
this._apiKey = await promptForAPIKey(GeminiNativeBYOKLMProvider.providerName, await this._byokStorageService.getAPIKey(GeminiNativeBYOKLMProvider.providerName) !== undefined);
63-
if (this._apiKey) {
64-
await this._byokStorageService.storeAPIKey(GeminiNativeBYOKLMProvider.providerName, this._apiKey, BYOKAuthType.GlobalApiKey);
62+
const result = await handleAPIKeyUpdate(GeminiNativeBYOKLMProvider.providerName, this._byokStorageService, promptForAPIKey);
63+
if (!result.cancelled) {
64+
this._apiKey = result.apiKey;
65+
this._genAIClient = undefined;
6566
}
6667
}
6768

src/extension/byok/vscode-node/geminiProvider.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/extension/byok/vscode-node/groqProvider.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)