@@ -8,16 +8,23 @@ import * as vscode from 'vscode';
88import { IChatMLFetcher } from '../../../../platform/chat/common/chatMLFetcher' ;
99import { ChatFetchResponseType } from '../../../../platform/chat/common/commonTypes' ;
1010import { MockChatMLFetcher } from '../../../../platform/chat/test/common/mockChatMLFetcher' ;
11+ import { CopilotToken , createTestExtendedTokenInfo } from '../../../../platform/authentication/common/copilotToken' ;
12+ import { ICopilotTokenManager } from '../../../../platform/authentication/common/copilotTokenManager' ;
13+ import { IAutomodeService } from '../../../../platform/endpoint/node/automodeService' ;
1114import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider' ;
1215import { CustomDataPartMimeTypes } from '../../../../platform/endpoint/common/endpointTypes' ;
1316import { CopilotChatEndpoint } from '../../../../platform/endpoint/node/copilotChatEndpoint' ;
17+ import { IEnvService } from '../../../../platform/env/common/envService' ;
1418import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext' ;
1519import { IChatEndpoint } from '../../../../platform/networking/common/networking' ;
1620import { ITestingServicesAccessor } from '../../../../platform/test/node/services' ;
21+ import { TokenizerType } from '../../../../util/common/tokenizer' ;
22+ import { DeferredPromise , raceTimeout } from '../../../../util/vs/base/common/async' ;
1723import { CancellationToken } from '../../../../util/vs/base/common/cancellation' ;
24+ import { Event } from '../../../../util/vs/base/common/event' ;
1825import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation' ;
1926import { createExtensionTestingServices } from '../../../test/vscode-node/services' ;
20- import { buildUtilityAliasModelInfo , CopilotLanguageModelWrapper } from '../languageModelAccess' ;
27+ import { buildUtilityAliasModelInfo , CopilotLanguageModelWrapper , LanguageModelAccess } from '../languageModelAccess' ;
2128
2229
2330suite ( 'CopilotLanguageModelWrapper' , ( ) => {
@@ -137,6 +144,111 @@ suite('CopilotLanguageModelWrapper', () => {
137144 } ) ;
138145} ) ;
139146
147+ suite ( 'LanguageModelAccess model info' , ( ) => {
148+ test ( 'does not wait for utility alias endpoint resolution' , async ( ) => {
149+ const aliasLookupStarted = new DeferredPromise < void > ( ) ;
150+ const unresolvedAliasEndpoint = new DeferredPromise < IChatEndpoint > ( ) ;
151+ const endpoint = {
152+ model : 'gpt-4o-mini' ,
153+ name : 'GPT 4o mini' ,
154+ family : 'gpt-4o-mini' ,
155+ version : '2024-07-18' ,
156+ modelProvider : 'copilot' ,
157+ modelMaxPromptTokens : 128_000 ,
158+ maxOutputTokens : 4_096 ,
159+ supportsToolCalls : true ,
160+ supportsVision : false ,
161+ supportsPrediction : false ,
162+ showInModelPicker : false ,
163+ isFallback : false ,
164+ tokenizer : TokenizerType . O200K ,
165+ urlOrRequestMetadata : '' ,
166+ } as unknown as IChatEndpoint ;
167+ const copilotToken = new CopilotToken ( createTestExtendedTokenInfo ( { token : 'token' , username : 'fake' , copilot_plan : 'unknown' } ) ) ;
168+ const testingServiceCollection = createExtensionTestingServices ( ) ;
169+ testingServiceCollection . define ( ICopilotTokenManager , {
170+ _serviceBrand : undefined ,
171+ onDidCopilotTokenRefresh : Event . None ,
172+ getCopilotToken : async ( ) => copilotToken ,
173+ resetCopilotToken : ( ) => { } ,
174+ } as unknown as ICopilotTokenManager ) ;
175+ testingServiceCollection . define ( IAutomodeService , {
176+ _serviceBrand : undefined ,
177+ resolveAutoModeEndpoint : async ( ) => endpoint ,
178+ invalidateRouterCache : ( ) => { } ,
179+ } as unknown as IAutomodeService ) ;
180+ testingServiceCollection . define ( IEndpointProvider , {
181+ _serviceBrand : undefined ,
182+ onDidModelsRefresh : Event . None ,
183+ getAllCompletionModels : async ( ) => [ ] ,
184+ getAllChatEndpoints : async ( ) => [ endpoint ] ,
185+ getChatEndpoint : async ( requestOrFamily : unknown ) => {
186+ if ( typeof requestOrFamily === 'string' ) {
187+ void aliasLookupStarted . complete ( ) ;
188+ return unresolvedAliasEndpoint . p ;
189+ }
190+ return endpoint ;
191+ } ,
192+ getEmbeddingsEndpoint : async ( ) => { throw new Error ( 'Not implemented in test' ) ; } ,
193+ } as unknown as IEndpointProvider ) ;
194+ const accessor = testingServiceCollection . createTestingAccessor ( ) ;
195+ // Pre-populate the prompt base-count cache so that
196+ // `_provideLanguageModelChatInfo`'s per-endpoint base-count lookup
197+ // resolves synchronously from cache rather than spinning up the
198+ // real tokenizer (which is slow and not relevant to this test).
199+ const extensionContext = accessor . get ( IVSCodeExtensionContext ) ;
200+ const baseCountCacheKey = 'lmBaseCount/gpt-4o-mini' ;
201+ await extensionContext . globalState . update ( baseCountCacheKey , { extensionVersion : accessor . get ( IEnvService ) . getVersion ( ) , baseCount : 0 } ) ;
202+ const languageModelAccess = accessor . get ( IInstantiationService ) . createInstance ( LanguageModelAccess ) ;
203+ try {
204+ const modelInfo = ( languageModelAccess as unknown as { _provideLanguageModelChatInfo ( options : { silent : boolean } , token : vscode . CancellationToken ) : Promise < vscode . LanguageModelChatInformation [ ] > } ) . _provideLanguageModelChatInfo ( { silent : true } , CancellationToken . None ) ;
205+ const resolved = await raceTimeout ( modelInfo , 2_000 ) ;
206+ assert . ok ( resolved , 'provideLanguageModelChatInfo did not resolve while utility alias lookup was pending' ) ;
207+ assert . deepStrictEqual ( resolved . map ( model => model . id ) , [ 'gpt-4o-mini' ] ) ;
208+ assert . ok ( aliasLookupStarted . isResolved , 'expected utility alias lookup to have been started in the background' ) ;
209+ } finally {
210+ languageModelAccess . dispose ( ) ;
211+ await extensionContext . globalState . update ( baseCountCacheKey , undefined ) ;
212+ }
213+ } ) ;
214+
215+ test ( 'refreshes utility aliases when an override uses the same model id from another provider' , async ( ) => {
216+ const publishedEndpoint = {
217+ model : 'gpt-4o-mini' ,
218+ modelProvider : 'copilot' ,
219+ } as IChatEndpoint ;
220+ const resolvedEndpoint = {
221+ model : 'gpt-4o-mini' ,
222+ modelProvider : 'azure' ,
223+ } as IChatEndpoint ;
224+ const testingServiceCollection = createExtensionTestingServices ( ) ;
225+ testingServiceCollection . define ( IEndpointProvider , {
226+ _serviceBrand : undefined ,
227+ onDidModelsRefresh : Event . None ,
228+ getAllCompletionModels : async ( ) => [ ] ,
229+ getAllChatEndpoints : async ( ) => [ ] ,
230+ getChatEndpoint : async ( ) => resolvedEndpoint ,
231+ getEmbeddingsEndpoint : async ( ) => { throw new Error ( 'Not implemented in test' ) ; } ,
232+ } as unknown as IEndpointProvider ) ;
233+ const accessor = testingServiceCollection . createTestingAccessor ( ) ;
234+ const languageModelAccess = accessor . get ( IInstantiationService ) . createInstance ( LanguageModelAccess ) ;
235+ const internals = languageModelAccess as unknown as {
236+ _utilityAliasEndpoints : Map < string , IChatEndpoint > ;
237+ _resolvedUtilityEndpoints : Map < string , { endpoint : IChatEndpoint ; baseCount : number } > ;
238+ _promptBaseCountCache : { getBaseCount ( endpoint : IChatEndpoint ) : Promise < number > } ;
239+ _refreshUtilityOverrides ( ) : Promise < void > ;
240+ } ;
241+ internals . _utilityAliasEndpoints . set ( 'copilot-utility-small' , publishedEndpoint ) ;
242+ internals . _promptBaseCountCache = { getBaseCount : async ( ) => 0 } ;
243+ try {
244+ await internals . _refreshUtilityOverrides ( ) ;
245+ assert . strictEqual ( internals . _resolvedUtilityEndpoints . get ( 'copilot-utility-small' ) ?. endpoint , resolvedEndpoint ) ;
246+ } finally {
247+ languageModelAccess . dispose ( ) ;
248+ }
249+ } ) ;
250+ } ) ;
251+
140252suite ( 'buildUtilityAliasModelInfo' , ( ) => {
141253
142254 function makeEndpoint ( overrides : Partial < IChatEndpoint > ) : IChatEndpoint {
0 commit comments