From 785cf14deb4979a153b5573973f90c8b13038e35 Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Wed, 20 May 2026 21:31:51 +0545 Subject: [PATCH 1/5] fix: prevent BYOK model from conflicting with same-named Copilot model in picker When a BYOK model is named identically to a Copilot-native model (e.g. GPT-4.1), both dropdown items received the same ID, causing the picker to highlight both as selected simultaneously. Use a provider-prefixed picker ID for BYOK models so each item has a unique identity, and update the selection and lookup logic to match. Fixes #167 --- .../eclipse/ui/chat/ModelPickerGroupsBuilder.java | 2 +- .../eclipse/ui/chat/services/ModelService.java | 4 ++-- .../copilot/eclipse/ui/utils/ModelUtils.java | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java index 035a429f..364992c1 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java @@ -102,7 +102,7 @@ private static List buildModelDropdownItems(List mod List items = new ArrayList<>(); for (CopilotModel model : models) { String suffix = ModelUtils.getModelSuffix(model); - items.add(new DropdownItem.Builder().id(model.getModelName()).label(model.getModelName()).suffix(suffix) + items.add(new DropdownItem.Builder().id(ModelUtils.getPickerId(model)).label(model.getModelName()).suffix(suffix) .icon(resolveModelIcon(model)).hoverProvider(new ModelHoverContentProvider(model)).build()); } return items; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java index 5d1c74dc..f60de900 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java @@ -338,7 +338,7 @@ public void setActiveModel(String modelName) { CopilotModel foundModel = null; for (Map.Entry entry : currentModels.entrySet()) { - if (entry.getValue().getModelName().equals(modelName)) { + if (ModelUtils.getPickerId(entry.getValue()).equals(modelName)) { compositeKey = entry.getKey(); foundModel = entry.getValue(); break; @@ -444,7 +444,7 @@ public void bindModelPicker(final DropdownButton picker) { if (activeModel == null || picker.isDisposed()) { return; } - picker.setSelectedItemId(activeModel.getModelName()); + picker.setSelectedItemId(ModelUtils.getPickerId(activeModel)); String suffix = StringUtils.isNotBlank(activeModel.getDegradationReason()) ? " - " + activeModel.getDegradationReason() : ""; picker.setToolTipText(NLS.bind(Messages.chat_actionBar_modelPicker_Tooltip, suffix)); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java index d6e9b823..11a7df10 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java @@ -24,6 +24,17 @@ private ModelUtils() { // Private constructor to prevent instantiation } + /** + * Returns a unique picker ID for the given model. BYOK models include the provider name as a + * prefix to avoid collisions with Copilot-native models that share the same display name. + */ + public static String getPickerId(CopilotModel model) { + if (model.getProviderName() != null) { + return model.getProviderName() + "_" + model.getModelName(); + } + return model.getModelName(); + } + /** * Convert ByokModel to CopilotModel format for unified handling. */ From cf85ebe8ec12871a06e3ea32221030b94be86171 Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Wed, 20 May 2026 22:32:25 +0545 Subject: [PATCH 2/5] fix: support both picker ID and model name in setActiveModel lookup Existing internal callers pass plain model names (e.g. fallback model, custom mode event). Match by picker ID first, then fall back to model name so both call patterns keep working. --- .../copilot/eclipse/ui/chat/services/ModelService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java index f60de900..696aeda6 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java @@ -338,9 +338,10 @@ public void setActiveModel(String modelName) { CopilotModel foundModel = null; for (Map.Entry entry : currentModels.entrySet()) { - if (ModelUtils.getPickerId(entry.getValue()).equals(modelName)) { + CopilotModel candidate = entry.getValue(); + if (ModelUtils.getPickerId(candidate).equals(modelName) || candidate.getModelName().equals(modelName)) { compositeKey = entry.getKey(); - foundModel = entry.getValue(); + foundModel = candidate; break; } } @@ -392,7 +393,7 @@ public CopilotModel getFallbackModel() { */ public void setFallBackModelAsActiveModel() { if (fallbackModel != null) { - setActiveModel(fallbackModel.getModelName()); + setActiveModel(ModelUtils.getPickerId(fallbackModel)); } } From a8f7a38bc0fe07bab749d1d9a667896499ee4fee Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Thu, 21 May 2026 10:05:05 +0545 Subject: [PATCH 3/5] refactor: use existing getModelKey() for picker ID instead of custom concatenation Replaces the custom providerName + "_" + modelName concatenation with the model's existing getModelKey() method, which already produces a stable composite key used throughout the codebase. This avoids potential collisions from naive string concatenation and eliminates the blank-provider-name edge case. --- .../microsoft/copilot/eclipse/ui/utils/ModelUtils.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java index 425d18f7..f7ec3c38 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java @@ -29,14 +29,12 @@ private ModelUtils() { } /** - * Returns a unique picker ID for the given model. BYOK models include the provider name as a - * prefix to avoid collisions with Copilot-native models that share the same display name. + * Returns a unique picker ID for the given model. Uses the model's existing composite key + * (providerName_id for BYOK, id for native) to avoid collisions when a BYOK model shares + * the same display name as a Copilot-native model. */ public static String getPickerId(CopilotModel model) { - if (model.getProviderName() != null) { - return model.getProviderName() + "_" + model.getModelName(); - } - return model.getModelName(); + return model.getModelKey(); } /** From e5d875c584fd6fc2cf3712c940751c3da821a3a3 Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Thu, 21 May 2026 14:26:38 +0545 Subject: [PATCH 4/5] refactor: Remove unused message keys and properties across various modules (#208) Replace getPickerId() wrapper with direct model.getModelKey() calls at each use site, removing the unnecessary indirection. --- .../eclipse/ui/chat/ModelPickerGroupsBuilder.java | 2 +- .../copilot/eclipse/ui/chat/services/ModelService.java | 6 +++--- .../microsoft/copilot/eclipse/ui/utils/ModelUtils.java | 9 --------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java index 96dc73a5..705ec253 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ModelPickerGroupsBuilder.java @@ -130,7 +130,7 @@ private static List buildModelDropdownItems(List mod String selectedLabel = StringUtils.isNotBlank(effortLevel) && StringUtils.isNotBlank(name) ? name + " - " + effortLevel : null; - items.add(new DropdownItem.Builder().id(ModelUtils.getPickerId(model)).label(name).selectedLabel(selectedLabel).suffix(suffix) + items.add(new DropdownItem.Builder().id(model.getModelKey()).label(name).selectedLabel(selectedLabel).suffix(suffix) .icon(resolveModelIcon(model)).hoverProvider(new ModelHoverContentProvider(model)).build()); } return items; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java index e9d1f490..20331df8 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java @@ -343,7 +343,7 @@ public void setActiveModel(String modelName) { for (Map.Entry entry : currentModels.entrySet()) { CopilotModel candidate = entry.getValue(); - if (ModelUtils.getPickerId(candidate).equals(modelName) || candidate.getModelName().equals(modelName)) { + if (candidate.getModelKey().equals(modelName) || candidate.getModelName().equals(modelName)) { compositeKey = entry.getKey(); foundModel = candidate; break; @@ -397,7 +397,7 @@ public CopilotModel getFallbackModel() { */ public void setFallBackModelAsActiveModel() { if (fallbackModel != null) { - setActiveModel(ModelUtils.getPickerId(fallbackModel)); + setActiveModel(fallbackModel.getModelKey()); } } @@ -552,7 +552,7 @@ public void bindModelPicker(final DropdownButton picker) { if (activeModel == null || picker.isDisposed()) { return; } - picker.setSelectedItemId(ModelUtils.getPickerId(activeModel)); + picker.setSelectedItemId(activeModel.getModelKey()); String suffix = StringUtils.isNotBlank(activeModel.getDegradationReason()) ? " - " + activeModel.getDegradationReason() : ""; picker.setToolTipText(NLS.bind(Messages.chat_actionBar_modelPicker_Tooltip, suffix)); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java index f7ec3c38..e7d18484 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/ModelUtils.java @@ -28,15 +28,6 @@ private ModelUtils() { // Private constructor to prevent instantiation } - /** - * Returns a unique picker ID for the given model. Uses the model's existing composite key - * (providerName_id for BYOK, id for native) to avoid collisions when a BYOK model shares - * the same display name as a Copilot-native model. - */ - public static String getPickerId(CopilotModel model) { - return model.getModelKey(); - } - /** * Convert ByokModel to CopilotModel format for unified handling. */ From b9366cd72df8895bf4c1d94b2cd2a1f362833c74 Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Fri, 22 May 2026 12:56:43 +0545 Subject: [PATCH 5/5] fix: make setActiveModel key-only and fix remaining name callers - setActiveModel now accepts only a composite model key (modelKey), matching the picker ID used throughout the codebase - Custom mode event handler resolves the model name to a key via findModelKeyByName() before calling setActiveModel - ModelHoverContentProvider uses model.getModelKey() directly instead of model.getModelName() --- .../ui/chat/services/ModelService.java | 34 ++++++++++++------- .../ui/swt/ModelHoverContentProvider.java | 2 +- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java index 20331df8..0cb4708b 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ModelService.java @@ -146,8 +146,11 @@ private void initializeEventHandlers() { currentChatMode = ChatMode.Agent; updateModelsForChatMode(ChatMode.Agent); - // Then switch to the specified model (setActiveModel will be called after models are loaded) - setActiveModel(modelName); + // Resolve name to key, then activate + String modelKey = findModelKeyByName(modelName); + if (modelKey != null) { + setActiveModel(modelKey); + } } }; } @@ -328,26 +331,31 @@ private void onDidCopilotStatusChange(CopilotStatusResult copilotStatusResult) { } } + private String findModelKeyByName(String modelName) { + Map currentModels = modelObservable.getValue(); + for (Map.Entry entry : currentModels.entrySet()) { + if (entry.getValue().getModelName().equals(modelName)) { + return entry.getKey(); + } + } + return null; + } + /** - * Set the active model by name. + * Set the active model by its composite key. * - * @param modelName the name of the model + * @param modelKey the composite key of the model */ - public void setActiveModel(String modelName) { + public void setActiveModel(String modelKey) { Map currentModels = modelObservable.getValue(); - // Find model by model name and get its composite key String compositeKey = null; final CopilotModel model; CopilotModel foundModel = null; - for (Map.Entry entry : currentModels.entrySet()) { - CopilotModel candidate = entry.getValue(); - if (candidate.getModelKey().equals(modelName) || candidate.getModelName().equals(modelName)) { - compositeKey = entry.getKey(); - foundModel = candidate; - break; - } + if (currentModels.containsKey(modelKey)) { + compositeKey = modelKey; + foundModel = currentModels.get(modelKey); } model = foundModel; if (model != null && compositeKey != null) { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java index a1698ffe..e0690211 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/ModelHoverContentProvider.java @@ -278,7 +278,7 @@ public void mouseDown(MouseEvent e) { // to update its label/suffix so the dropdown control reflects the (model, effort) pair the user just // chose -- even when they clicked an effort on a non-active model. modelService.setSelectedReasoningEffort(model, effort); - modelService.setActiveModel(model.getModelName()); + modelService.setActiveModel(model.getModelKey()); // Close the entire dropdown (hover + main popup) via the host-provided callback so the user sees an // immediate dismiss. Next time the dropdown opens, refreshBoundModelPickers (invoked from // setSelectedReasoningEffort) has updated the model row's suffix to reflect the newly selected effort.