@@ -310,6 +310,58 @@ function parseModelKey(compositeKey: string): { provider: string; modelId: strin
310310 return { provider : compositeKey . slice ( 0 , slashIdx ) , modelId : compositeKey . slice ( slashIdx + 1 ) }
311311}
312312
313+ const MODEL_PROVIDER_PRIORITY = [
314+ 'anthropic' ,
315+ 'bedrock' ,
316+ 'azure-anthropic' ,
317+ 'openai' ,
318+ 'azure-openai' ,
319+ 'gemini' ,
320+ 'google' ,
321+ 'azure' ,
322+ 'unknown' ,
323+ ] as const
324+
325+ const KNOWN_COPILOT_PROVIDERS = new Set < string > ( MODEL_PROVIDER_PRIORITY )
326+
327+ function isCompositeModelId ( modelId : string ) : boolean {
328+ const slashIdx = modelId . indexOf ( '/' )
329+ if ( slashIdx <= 0 || slashIdx === modelId . length - 1 ) return false
330+ const provider = modelId . slice ( 0 , slashIdx )
331+ return KNOWN_COPILOT_PROVIDERS . has ( provider )
332+ }
333+
334+ function toCompositeModelId ( modelId : string , provider : string ) : string {
335+ if ( ! modelId ) return modelId
336+ return isCompositeModelId ( modelId ) ? modelId : `${ provider } /${ modelId } `
337+ }
338+
339+ function pickPreferredProviderModel ( matches : AvailableModel [ ] ) : AvailableModel | undefined {
340+ for ( const provider of MODEL_PROVIDER_PRIORITY ) {
341+ const found = matches . find ( ( m ) => m . provider === provider )
342+ if ( found ) return found
343+ }
344+ return matches [ 0 ]
345+ }
346+
347+ function normalizeSelectedModelKey ( selectedModel : string , models : AvailableModel [ ] ) : string {
348+ if ( ! selectedModel || models . length === 0 ) return selectedModel
349+ if ( models . some ( ( m ) => m . id === selectedModel ) ) return selectedModel
350+
351+ const { provider, modelId } = parseModelKey ( selectedModel )
352+ const targetModelId = modelId || selectedModel
353+
354+ const matches = models . filter ( ( m ) => m . id . endsWith ( `/${ targetModelId } ` ) )
355+ if ( matches . length === 0 ) return selectedModel
356+
357+ if ( provider ) {
358+ const sameProvider = matches . find ( ( m ) => m . provider === provider )
359+ if ( sameProvider ) return sameProvider . id
360+ }
361+
362+ return ( pickPreferredProviderModel ( matches ) ?? matches [ 0 ] ) . id
363+ }
364+
313365/** Look up the provider for the currently selected model from the composite key. */
314366function getSelectedProvider ( get : CopilotGet ) : string | undefined {
315367 const { provider } = parseModelKey ( get ( ) . selectedModel )
@@ -2230,6 +2282,7 @@ export const useCopilotStore = create<CopilotStore>()(
22302282 const data = await response . json ( )
22312283 const models : unknown [ ] = Array . isArray ( data ?. models ) ? data . models : [ ]
22322284
2285+ const seenModelIds = new Set < string > ( )
22332286 const normalizedModels : AvailableModel [ ] = models
22342287 . filter ( ( model : unknown ) : model is AvailableModel => {
22352288 return (
@@ -2240,27 +2293,35 @@ export const useCopilotStore = create<CopilotStore>()(
22402293 )
22412294 } )
22422295 . map ( ( model : AvailableModel ) => {
2243- const provider = model . provider || 'unknown'
2244- // Use composite provider/modelId keys (matching agent block pattern in providers/models.ts)
2245- // so models with the same raw ID from different providers are uniquely identified.
2246- const compositeId = `${ provider } /${ model . id } `
2296+ const idProvider = isCompositeModelId ( model . id ) ? parseModelKey ( model . id ) . provider : ''
2297+ const provider = model . provider || idProvider || 'unknown'
2298+ // Use stable composite provider/modelId keys so same model IDs from different
2299+ // providers remain uniquely addressable.
2300+ const compositeId = toCompositeModelId ( model . id , provider )
22472301 return {
22482302 id : compositeId ,
22492303 friendlyName : model . friendlyName || model . id ,
22502304 provider,
22512305 }
22522306 } )
2307+ . filter ( ( model ) => {
2308+ if ( seenModelIds . has ( model . id ) ) return false
2309+ seenModelIds . add ( model . id )
2310+ return true
2311+ } )
22532312
22542313 const { selectedModel } = get ( )
2255- const selectedModelExists = normalizedModels . some ( ( model ) => model . id === selectedModel )
2314+ const normalizedSelectedModel = normalizeSelectedModelKey ( selectedModel , normalizedModels )
2315+ const selectedModelExists = normalizedModels . some (
2316+ ( model ) => model . id === normalizedSelectedModel
2317+ )
22562318
22572319 // Pick the best default: prefer claude-opus-4-6 with provider priority:
22582320 // direct anthropic > bedrock > azure-anthropic > any other.
2259- let nextSelectedModel = selectedModel
2321+ let nextSelectedModel = normalizedSelectedModel
22602322 if ( ! selectedModelExists && normalizedModels . length > 0 ) {
2261- const providerPriority = [ 'anthropic' , 'bedrock' , 'azure-anthropic' ]
22622323 let opus46 : AvailableModel | undefined
2263- for ( const prov of providerPriority ) {
2324+ for ( const prov of MODEL_PROVIDER_PRIORITY ) {
22642325 opus46 = normalizedModels . find ( ( m ) => m . id === `${ prov } /claude-opus-4-6` )
22652326 if ( opus46 ) break
22662327 }
0 commit comments