@@ -217,4 +217,76 @@ describe('GeminiNativeBYOKLMProvider', () => {
217217 tokenSource . token
218218 ) ) . rejects . toThrow ( / N o A P I k e y c o n f i g u r e d / i) ;
219219 } ) ;
220+
221+ it ( 'prompts for a new API key when listing models fails with an invalid key' , async ( ) => {
222+ const { GeminiNativeBYOKLMProvider } = await import ( '../geminiNativeProvider' ) ;
223+ const genai = await import ( '@google/genai' ) ;
224+ const MockGoogleGenAI = genai . GoogleGenAI as unknown as { listModelsResult : AsyncIterable < any > } ;
225+ // Simulate the models.list() call throwing an invalid API key error when iterated
226+ MockGoogleGenAI . listModelsResult = ( async function * ( ) {
227+ throw new Error ( 'ApiError: {"error":{"message":"API key not valid. Please pass a valid API key.","details":[{"reason":"API_KEY_INVALID"}]}}' ) ;
228+ } ) ( ) ;
229+
230+ const storage = createStorageService ( {
231+ getAPIKey : vi . fn ( ) . mockResolvedValue ( 'bad_key' ) ,
232+ } ) ;
233+
234+ mockHandleAPIKeyUpdate . mockResolvedValue ( { apiKey : undefined , deleted : false , cancelled : true } ) ;
235+
236+ const provider = new GeminiNativeBYOKLMProvider ( undefined , storage , new TestLogService ( ) , createRequestLogger ( ) ) ;
237+ const tokenSource = new vscode . CancellationTokenSource ( ) ;
238+ const models = await provider . provideLanguageModelChatInformation ( { silent : false } , tokenSource . token ) ;
239+
240+ // When the key is invalid, we should re-prompt for a new one
241+ // and handle the failure gracefully by returning an empty list.
242+ expect ( models ) . toEqual ( [ ] ) ;
243+ expect ( mockHandleAPIKeyUpdate ) . toHaveBeenCalled ( ) ;
244+ } ) ;
245+
246+ it ( 'retries listing models after re-prompting with a valid API key' , async ( ) => {
247+ const { GeminiNativeBYOKLMProvider } = await import ( '../geminiNativeProvider' ) ;
248+ const genai = await import ( '@google/genai' ) ;
249+ const MockGoogleGenAI = genai . GoogleGenAI as unknown as { listModelsResult : AsyncIterable < any > } ;
250+
251+ let iterationCount = 0 ;
252+ let hasThrown = false ;
253+ const modelId = 'test-model' ;
254+
255+ MockGoogleGenAI . listModelsResult = {
256+ async * [ Symbol . asyncIterator ] ( ) {
257+ iterationCount ++ ;
258+ if ( ! hasThrown ) {
259+ hasThrown = true ;
260+ throw new Error ( 'ApiError: {"error":{"message":"API key not valid. Please pass a valid API key.","details":[{"reason":"API_KEY_INVALID"}]}}' ) ;
261+ }
262+ yield { name : modelId } ;
263+ }
264+ } ;
265+
266+ const storage = createStorageService ( {
267+ getAPIKey : vi . fn ( ) . mockResolvedValue ( 'bad_key' ) ,
268+ } ) ;
269+
270+ mockHandleAPIKeyUpdate . mockResolvedValue ( { apiKey : 'k_new' , deleted : false , cancelled : false } ) ;
271+
272+ const knownModels = {
273+ [ modelId ] : {
274+ name : 'Test Model' ,
275+ maxInputTokens : 1000 ,
276+ maxOutputTokens : 1000 ,
277+ toolCalling : false ,
278+ vision : false
279+ }
280+ } ;
281+
282+ const provider = new GeminiNativeBYOKLMProvider ( knownModels , storage , new TestLogService ( ) , createRequestLogger ( ) ) ;
283+ const tokenSource = new vscode . CancellationTokenSource ( ) ;
284+ const models = await provider . provideLanguageModelChatInformation ( { silent : false } , tokenSource . token ) ;
285+
286+ // First attempt should fail with invalid key, then after re-prompting
287+ // we should retry listing models and succeed with the new key.
288+ expect ( models . map ( m => m . id ) ) . toEqual ( [ modelId ] ) ;
289+ expect ( iterationCount ) . toBe ( 2 ) ;
290+ expect ( mockHandleAPIKeyUpdate ) . toHaveBeenCalled ( ) ;
291+ } ) ;
220292} ) ;
0 commit comments