@@ -295,4 +295,183 @@ describe("webviewMessageHandler - requestRouterModels provider filter", () => {
295295 baseUrl : "http://stored:4000" ,
296296 } )
297297 } )
298+
299+ it ( "fetches Moonshot models when stored Moonshot credentials exist" , async ( ) => {
300+ mockProvider . getState . mockResolvedValue ( {
301+ apiConfiguration : {
302+ moonshotApiKey : "stored-moonshot-key" ,
303+ moonshotBaseUrl : "https://api.moonshot.ai/v1" ,
304+ } ,
305+ } )
306+
307+ getModelsMock . mockImplementation ( async ( options : any ) => {
308+ if ( options ?. provider === "moonshot" ) {
309+ return { "kimi-k2-0905-preview" : { contextWindow : 262144 , supportsPromptCache : true } }
310+ }
311+
312+ switch ( options ?. provider ) {
313+ case "openrouter" :
314+ return { "openrouter/qwen2.5" : { contextWindow : 32768 , supportsPromptCache : false } }
315+ case "requesty" :
316+ return { "requesty/model" : { contextWindow : 8192 , supportsPromptCache : false } }
317+ case "vercel-ai-gateway" :
318+ return { "vercel/model" : { contextWindow : 8192 , supportsPromptCache : false } }
319+ case "litellm" :
320+ return { "litellm/model" : { contextWindow : 8192 , supportsPromptCache : false } }
321+ default :
322+ return { }
323+ }
324+ } )
325+
326+ await webviewMessageHandler (
327+ mockProvider as any ,
328+ {
329+ type : "requestRouterModels" ,
330+ } as any ,
331+ )
332+
333+ expect ( getModelsMock ) . toHaveBeenCalledWith ( {
334+ provider : "moonshot" ,
335+ apiKey : "stored-moonshot-key" ,
336+ baseUrl : "https://api.moonshot.ai/v1" ,
337+ } )
338+
339+ const call = ( mockProvider . postMessageToWebview as any ) . mock . calls . find (
340+ ( c : any [ ] ) => c [ 0 ] ?. type === "routerModels" ,
341+ )
342+ expect ( call ) . toBeTruthy ( )
343+ expect ( call [ 0 ] . routerModels . moonshot ) . toEqual ( {
344+ "kimi-k2-0905-preview" : { contextWindow : 262144 , supportsPromptCache : true } ,
345+ } )
346+ } )
347+
348+ it ( "flushes Moonshot cache when explicit apiKey provided via message values" , async ( ) => {
349+ getModelsMock . mockResolvedValue ( {
350+ "kimi-k2-0905-preview" : { contextWindow : 262144 , supportsPromptCache : true } ,
351+ } )
352+
353+ await webviewMessageHandler (
354+ mockProvider as any ,
355+ {
356+ type : "requestRouterModels" ,
357+ values : {
358+ moonshotApiKey : "new-moonshot-key" ,
359+ moonshotBaseUrl : "https://api.moonshot.cn/v1" ,
360+ } ,
361+ } as any ,
362+ )
363+
364+ // flushModels should have been called for moonshot
365+ const moonshotFlushCalls = flushModelsMock . mock . calls . filter ( ( c : any [ ] ) => c [ 0 ] ?. provider === "moonshot" )
366+ expect ( moonshotFlushCalls . length ) . toBe ( 1 )
367+ expect ( moonshotFlushCalls [ 0 ] [ 0 ] ) . toEqual ( {
368+ provider : "moonshot" ,
369+ apiKey : "new-moonshot-key" ,
370+ baseUrl : "https://api.moonshot.cn/v1" ,
371+ } )
372+
373+ // getModels should use the provided credentials
374+ const moonshotCalls = getModelsMock . mock . calls . filter ( ( c : any [ ] ) => c [ 0 ] ?. provider === "moonshot" )
375+ expect ( moonshotCalls . length ) . toBe ( 1 )
376+ expect ( moonshotCalls [ 0 ] [ 0 ] ) . toEqual ( {
377+ provider : "moonshot" ,
378+ apiKey : "new-moonshot-key" ,
379+ baseUrl : "https://api.moonshot.cn/v1" ,
380+ } )
381+ } )
382+
383+ it ( "does not flush Moonshot cache when using stored credentials" , async ( ) => {
384+ mockProvider . getState . mockResolvedValue ( {
385+ apiConfiguration : {
386+ moonshotApiKey : "stored-moonshot-key" ,
387+ } ,
388+ } )
389+
390+ getModelsMock . mockImplementation ( async ( options : any ) => {
391+ if ( options ?. provider === "moonshot" ) {
392+ return { "kimi-k2-0905-preview" : { contextWindow : 262144 , supportsPromptCache : true } }
393+ }
394+
395+ switch ( options ?. provider ) {
396+ case "openrouter" :
397+ return { "openrouter/qwen2.5" : { contextWindow : 32768 , supportsPromptCache : false } }
398+ case "requesty" :
399+ return { "requesty/model" : { contextWindow : 8192 , supportsPromptCache : false } }
400+ case "vercel-ai-gateway" :
401+ return { "vercel/model" : { contextWindow : 8192 , supportsPromptCache : false } }
402+ case "litellm" :
403+ return { "litellm/model" : { contextWindow : 8192 , supportsPromptCache : false } }
404+ default :
405+ return { }
406+ }
407+ } )
408+
409+ await webviewMessageHandler (
410+ mockProvider as any ,
411+ {
412+ type : "requestRouterModels" ,
413+ } as any ,
414+ )
415+
416+ // flushModels should NOT have been called for moonshot
417+ const moonshotFlushCalls = flushModelsMock . mock . calls . filter ( ( c : any [ ] ) => c [ 0 ] ?. provider === "moonshot" )
418+ expect ( moonshotFlushCalls . length ) . toBe ( 0 )
419+
420+ // getModels should still have been called with stored credentials
421+ const moonshotCalls = getModelsMock . mock . calls . filter ( ( c : any [ ] ) => c [ 0 ] ?. provider === "moonshot" )
422+ expect ( moonshotCalls . length ) . toBe ( 1 )
423+ expect ( moonshotCalls [ 0 ] [ 0 ] ) . toEqual ( {
424+ provider : "moonshot" ,
425+ apiKey : "stored-moonshot-key" ,
426+ baseUrl : undefined ,
427+ } )
428+ } )
429+
430+ it ( "posts a Moonshot provider error and keeps an empty aggregate entry when fetch fails" , async ( ) => {
431+ mockProvider . getState . mockResolvedValue ( {
432+ apiConfiguration : {
433+ moonshotApiKey : "stored-moonshot-key" ,
434+ } ,
435+ } )
436+
437+ getModelsMock . mockImplementation ( async ( options : any ) => {
438+ if ( options ?. provider === "moonshot" ) {
439+ throw new Error ( "Moonshot API error" )
440+ }
441+
442+ switch ( options ?. provider ) {
443+ case "openrouter" :
444+ return { "openrouter/qwen2.5" : { contextWindow : 32768 , supportsPromptCache : false } }
445+ case "requesty" :
446+ return { "requesty/model" : { contextWindow : 8192 , supportsPromptCache : false } }
447+ case "vercel-ai-gateway" :
448+ return { "vercel/model" : { contextWindow : 8192 , supportsPromptCache : false } }
449+ case "litellm" :
450+ return { "litellm/model" : { contextWindow : 8192 , supportsPromptCache : false } }
451+ default :
452+ return { }
453+ }
454+ } )
455+
456+ await webviewMessageHandler (
457+ mockProvider as any ,
458+ {
459+ type : "requestRouterModels" ,
460+ } as any ,
461+ )
462+
463+ // Should have posted an error for moonshot
464+ const errorCall = ( mockProvider . postMessageToWebview as any ) . mock . calls . find (
465+ ( c : any [ ] ) => c [ 0 ] ?. type === "singleRouterModelFetchResponse" && c [ 0 ] ?. values ?. provider === "moonshot" ,
466+ )
467+ expect ( errorCall ) . toBeTruthy ( )
468+ expect ( errorCall [ 0 ] . success ) . toBe ( false )
469+
470+ // Aggregate entry should still be empty
471+ const call = ( mockProvider . postMessageToWebview as any ) . mock . calls . find (
472+ ( c : any [ ] ) => c [ 0 ] ?. type === "routerModels" ,
473+ )
474+ expect ( call ) . toBeTruthy ( )
475+ expect ( call [ 0 ] . routerModels . moonshot ) . toEqual ( { } )
476+ } )
298477} )
0 commit comments