From 0aeeecf4ede0a4f6ca2063cad41b3a8b65753790 Mon Sep 17 00:00:00 2001 From: liudaac Date: Fri, 3 Jul 2026 00:28:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89Mo?= =?UTF-8?q?del?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liudaac --- .../copilot-agent/package.json | 2 +- .../core/lsp/LsStreamConnectionProvider.java | 156 ++++++++++++++++++ .../core/lsp/protocol/byok/ByokModel.java | 15 +- .../lsp/protocol/byok/ByokModelProvider.java | 12 +- .../eclipse/ui/chat/services/ByokService.java | 4 +- .../ui/preferences/AddByokModelDialog.java | 38 +++-- .../AutoApprovePreferencePage.java | 34 +--- .../ui/preferences/ByokPreferencePage.java | 20 +-- .../ui/preferences/McpPreferencePage.java | 2 +- .../eclipse/ui/preferences/Messages.java | 1 + .../ui/preferences/PreferencePageUtils.java | 9 - .../ui/preferences/messages.properties | 1 + 12 files changed, 225 insertions(+), 69 deletions(-) diff --git a/com.microsoft.copilot.eclipse.core/copilot-agent/package.json b/com.microsoft.copilot.eclipse.core/copilot-agent/package.json index 7702b184..3e361fd1 100644 --- a/com.microsoft.copilot.eclipse.core/copilot-agent/package.json +++ b/com.microsoft.copilot.eclipse.core/copilot-agent/package.json @@ -9,7 +9,7 @@ "url": "https://github.com/microsoft/copilot-for-eclipse.git" }, "scripts": { - "postinstall": "node copy-binaries.js" + "postinstall": "node copy-binaries.js && node patch-custom-openai-byok.js" }, "dependencies": { "@github/copilot-language-server": "1.502.5", diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java index 6f637137..de2657a6 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/LsStreamConnectionProvider.java @@ -61,6 +61,12 @@ public Object getInitializationOptions(@Nullable URI rootUri) { @Override public void start() throws IOException { + if (!"true".equalsIgnoreCase(System.getenv("COPILOT_USE_BINARY_LSP"))) { + CopilotCore.LOGGER.info("Starting patched JS LSP agent. Set COPILOT_USE_BINARY_LSP=true to use binary agent."); + startJsLspAgent(); + return; + } + if ("true".equalsIgnoreCase(System.getenv("COPILOT_DEBUG_LOCAL"))) { CopilotCore.LOGGER.info("Forcing JS LSP agent start due to environment variable."); startJsLspAgent(); @@ -104,6 +110,7 @@ private void startBinaryLspAgent() throws IOException { } private void startJsLspAgent() throws IOException { + ensureBundledRipgrepExecutable(); this.setCommands(getJavaScriptCommands()); super.start(); CopilotCore.LOGGER.info("JS agent started successfully."); @@ -175,6 +182,39 @@ private void enforceUtf8Charset(List commands) { } private @Nullable String findNodeAbsolutePath() throws IOException { + String nodePath = System.getenv("COPILOT_NODE_PATH"); + if (isUsableNode(nodePath)) { + return Path.of(nodePath).toString(); + } + + nodePath = System.getProperty("org.eclipse.wildwebdeveloper.nodeJSLocation"); + if (isUsableNode(nodePath)) { + return Path.of(nodePath).toString(); + } + + nodePath = findNodeInPath(System.getenv("PATH")); + if (nodePath != null) { + return nodePath; + } + + if (PlatformUtils.isMac()) { + Map loginShellEnvironment = getLoginShellEnvironment(); + nodePath = findNodeInPath(loginShellEnvironment.get("PATH")); + if (nodePath != null) { + return nodePath; + } + + nodePath = executeFirstLine(new String[] { "/bin/zsh", "-l", "-i", "-c", "command -v node" }); + if (isUsableNode(nodePath)) { + return Path.of(nodePath).toString(); + } + } + + nodePath = findNodeInDefaultLocations(); + if (nodePath != null) { + return nodePath; + } + try { // The 'wildwebdeveloper' bundle is optional for Eclipse. Ensure it is available before attempting to use it. Class.forName("org.eclipse.wildwebdeveloper.embedder.node.NodeJSManager"); @@ -190,6 +230,122 @@ private void enforceUtf8Charset(List commands) { return nodeJsLocation.getAbsolutePath(); } + private @Nullable String findNodeInPath(@Nullable String pathValue) { + if (StringUtils.isBlank(pathValue)) { + return null; + } + + String[] paths = pathValue.split(File.pathSeparator); + for (String path : paths) { + String nodePath = Path.of(path, PlatformUtils.isWindows() ? "node.exe" : "node").toString(); + if (isUsableNode(nodePath)) { + return nodePath; + } + } + return null; + } + + private @Nullable String findNodeInDefaultLocations() { + List candidatePaths = PlatformUtils.isWindows() + ? List.of("C:\\Program Files\\nodejs\\node.exe") + : List.of("/opt/homebrew/bin/node", "/usr/local/bin/node", "/usr/bin/node"); + + for (String candidatePath : candidatePaths) { + if (isUsableNode(candidatePath)) { + return Path.of(candidatePath).toString(); + } + } + return null; + } + + private boolean isUsableNode(@Nullable String nodePath) { + if (StringUtils.isBlank(nodePath)) { + return false; + } + + File node = Path.of(nodePath).toFile(); + if (!node.exists() || !node.canExecute()) { + return false; + } + + String version = executeFirstLine(new String[] { node.getAbsolutePath(), "-v" }); + if (StringUtils.isBlank(version)) { + return false; + } + + CopilotCore.LOGGER.info("Using Node.js for patched JS LSP agent: " + node.getAbsolutePath() + " (" + version + ")"); + return true; + } + + private @Nullable String executeFirstLine(String[] command) { + Process process = null; + try { + process = new ProcessBuilder(command).start(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + return reader.readLine(); + } + } catch (IOException e) { + CopilotCore.LOGGER.error("Unable to execute command: " + String.join(" ", command), e); + return null; + } finally { + if (process != null) { + process.destroy(); + } + } + } + + private void ensureBundledRipgrepExecutable() throws IOException { + Path rgPath = findBundledRipgrepPath(); + if (rgPath == null || !Files.exists(rgPath)) { + return; + } + + File executable = rgPath.toFile(); + if (!executable.canExecute() && !executable.setExecutable(true)) { + throw new IOException("Could not make bundled ripgrep executable: " + rgPath); + } + } + + private @Nullable Path findBundledRipgrepPath() { + Path distPath = findAgentDistDirectoryPath(); + if (distPath == null) { + return null; + } + + String osDirectory = getRipgrepOsDirectory(); + String archDirectory = getRipgrepArchDirectory(); + if (osDirectory == null || archDirectory == null) { + return null; + } + + String executableName = PlatformUtils.isWindows() ? "rg.exe" : "rg"; + return distPath.resolve("bin").resolve(osDirectory).resolve(archDirectory).resolve(executableName); + } + + private @Nullable String getRipgrepOsDirectory() { + if (PlatformUtils.isMac()) { + return "darwin"; + } + if (PlatformUtils.isLinux()) { + return "linux"; + } + if (PlatformUtils.isWindows()) { + return "win32"; + } + return null; + } + + private @Nullable String getRipgrepArchDirectory() { + if (PlatformUtils.isArm64()) { + return "arm64"; + } + if (PlatformUtils.isIntel64()) { + return "x64"; + } + return null; + } + private @Nullable String findJavaScriptLanguageServerPath() throws IOException { String lsJsPath = System.getenv("COPILOT_LS_JS_PATH"); if (StringUtils.isNotBlank(lsJsPath)) { diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModel.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModel.java index 290fa334..6154587b 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModel.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModel.java @@ -17,6 +17,7 @@ public class ByokModel { private boolean isRegistered; private boolean isCustomModel; private String deploymentUrl; + private String baseUrl; private String apiKey; private ByokModelCapabilities modelCapabilities; @@ -60,6 +61,14 @@ public void setDeploymentUrl(String deploymentUrl) { this.deploymentUrl = deploymentUrl; } + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + public ByokModelCapabilities getModelCapabilities() { return modelCapabilities; } @@ -82,7 +91,8 @@ public String getModelKey() { @Override public int hashCode() { - return Objects.hash(modelId, providerName, isRegistered, isCustomModel, deploymentUrl, modelCapabilities, apiKey); + return Objects.hash(modelId, providerName, isRegistered, isCustomModel, deploymentUrl, baseUrl, modelCapabilities, + apiKey); } @Override @@ -99,7 +109,7 @@ public boolean equals(Object obj) { ByokModel other = (ByokModel) obj; return Objects.equals(modelId, other.modelId) && Objects.equals(providerName, other.providerName) && isRegistered == other.isRegistered && isCustomModel == other.isCustomModel - && Objects.equals(deploymentUrl, other.deploymentUrl) + && Objects.equals(deploymentUrl, other.deploymentUrl) && Objects.equals(baseUrl, other.baseUrl) && Objects.equals(modelCapabilities, other.modelCapabilities) && Objects.equals(apiKey, other.apiKey); } @@ -111,6 +121,7 @@ public String toString() { builder.append("isRegistered", isRegistered); builder.append("isCustomModel", isCustomModel); builder.append("deploymentUrl", deploymentUrl); + builder.append("baseUrl", baseUrl); builder.append("modelCapabilities", modelCapabilities); builder.append("apiKey", apiKey); return builder.toString(); diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModelProvider.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModelProvider.java index 18eccd74..4a7e33ac 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModelProvider.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/byok/ByokModelProvider.java @@ -12,7 +12,8 @@ public enum ByokModelProvider { GEMINI("Gemini"), GROQ("Groq"), OPENROUTER("OpenRouter"), - ANTHROPIC("Anthropic"); + ANTHROPIC("Anthropic"), + CUSTOM_OPENAI("Custom OpenAI"); private final String displayName; @@ -33,6 +34,15 @@ public static boolean isAzure(String providerDisplayName) { return AZURE.getDisplayName().equals(providerDisplayName); } + /** + * Utility to check whether a provider stores endpoint credentials on each model entry instead of using a single + * provider-level API key. + */ + public static boolean usesModelLevelCredentials(String providerDisplayName) { + return AZURE.getDisplayName().equals(providerDisplayName) + || CUSTOM_OPENAI.getDisplayName().equals(providerDisplayName); + } + @Override public String toString() { return displayName; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ByokService.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ByokService.java index 417cb6ed..faa24189 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ByokService.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ByokService.java @@ -257,7 +257,7 @@ public CompletableFuture deleteApiKey(String providerName) { * Reload a single provider's complete data (API keys, local models, and remote models if applicable). */ public CompletableFuture reloadProvider(String providerName) { - if (ByokModelProvider.isAzure(providerName)) { + if (ByokModelProvider.usesModelLevelCredentials(providerName)) { return loadLocalModels(); } @@ -369,7 +369,7 @@ private CompletableFuture fetchAllProvidersSequentially() { return; } List providersToFetch = currentApiKeys.keySet().stream() - .filter(providerName -> !ByokModelProvider.isAzure(providerName)).toList(); + .filter(providerName -> !ByokModelProvider.usesModelLevelCredentials(providerName)).toList(); providersRef.set(providersToFetch); }); List providersToFetch = providersRef.get(); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java index 4d20ddf6..22812aab 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AddByokModelDialog.java @@ -32,9 +32,11 @@ public class AddByokModelDialog extends TrayDialog { private static final int CONTAINER_WIDTH = 600; + private static final int DEFAULT_MAX_INPUT_TOKENS = 128_000; + private static final int DEFAULT_MAX_OUTPUT_TOKENS = 16_000; private Text modelIdText; - private Text deploymentUrlText; + private Text endpointUrlText; private Text apiKeyText; private Text displayNameText; private Button supportToolCallingCheck; @@ -85,8 +87,8 @@ protected Control createDialogArea(Composite parent) { modelIdText.addModifyListener(this::onFieldChanged); // Provider-specific fields - if (providerName.equals(ByokModelProvider.AZURE.getDisplayName())) { - createAzureSpecificFields(container); + if (ByokModelProvider.usesModelLevelCredentials(providerName)) { + createModelCredentialFields(container); } // Display Name (optional for all providers) @@ -114,12 +116,11 @@ protected Control createDialogArea(Composite parent) { return container; } - private void createAzureSpecificFields(Composite container) { - // Deployment URL * - new Label(container, SWT.NONE).setText(Messages.preferences_page_byok_addModel_deploymentUrl); - deploymentUrlText = new Text(container, SWT.BORDER); - deploymentUrlText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - deploymentUrlText.addModifyListener(this::onFieldChanged); + private void createModelCredentialFields(Composite container) { + new Label(container, SWT.NONE).setText(getEndpointLabel()); + endpointUrlText = new Text(container, SWT.BORDER); + endpointUrlText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + endpointUrlText.addModifyListener(this::onFieldChanged); // API Key * new Label(container, SWT.NONE).setText(Messages.preferences_page_byok_addModel_apiKey); @@ -154,6 +155,13 @@ private void createAzureSpecificFields(Composite container) { }); } + private String getEndpointLabel() { + if (ByokModelProvider.isAzure(providerName)) { + return Messages.preferences_page_byok_addModel_deploymentUrl; + } + return Messages.preferences_page_byok_addModel_baseUrl; + } + /** * Get the appropriate help context ID based on the provider. */ @@ -191,8 +199,8 @@ private boolean isValidInput() { return false; } - if (providerName.equals(ByokModelProvider.AZURE.getDisplayName())) { - if (deploymentUrlText == null || StringUtils.isBlank(deploymentUrlText.getText()) || apiKeyText == null + if (ByokModelProvider.usesModelLevelCredentials(providerName)) { + if (endpointUrlText == null || StringUtils.isBlank(endpointUrlText.getText()) || apiKeyText == null || StringUtils.isBlank(apiKeyText.getText())) { return false; } @@ -232,9 +240,11 @@ private ByokModel buildModel() { model.setCustomModel(true); // Set provider-specific fields - if (providerName.equals(ByokModelProvider.AZURE.getDisplayName()) && deploymentUrlText != null + if (ByokModelProvider.usesModelLevelCredentials(providerName) && endpointUrlText != null && apiKeyText != null) { - model.setDeploymentUrl(deploymentUrlText.getText().trim()); + String endpointUrl = endpointUrlText.getText().trim(); + model.setBaseUrl(endpointUrl); + model.setDeploymentUrl(endpointUrl); model.setApiKey(apiKeyText.getText().trim()); } @@ -243,6 +253,8 @@ private ByokModel buildModel() { String displayedName = displayNameText.getText().trim().isEmpty() ? modelIdText.getText().trim() : displayNameText.getText().trim(); capabilities.setName(displayedName); + capabilities.setMaxInputTokens(DEFAULT_MAX_INPUT_TOKENS); + capabilities.setMaxOutputTokens(DEFAULT_MAX_OUTPUT_TOKENS); capabilities.setToolCalling(supportToolCallingCheck.getSelection()); capabilities.setVision(supportVisionCheck.getSelection()); model.setModelCapabilities(capabilities); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java index fb0be2c0..b4b8b3f9 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/AutoApprovePreferencePage.java @@ -6,8 +6,6 @@ import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferencePage; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; @@ -53,28 +51,12 @@ protected Control createContents(Composite parent) { Messages.preferences_page_auto_approve_disabled_by_organization); } - // Wrap the sections in a height-capped ScrolledComposite. The override on - // computeSize bounds the height reported to the dialog's PageLayout so the - // Preferences shell stays stable; the real (taller) content scrolls here. - // This scroller is also the parent the section tables forward wheel events - // to (see forwardVerticalMouseWheelToParentScrollerAtBoundary). - ScrolledComposite scrolled = new ScrolledComposite(parent, SWT.V_SCROLL) { - @Override - public Point computeSize(int widthHint, int heightHint, boolean changed) { - Point size = super.computeSize(widthHint, heightHint, changed); - size.y = Math.min(size.y, PreferencePageUtils.STANDARD_CONTENT_HEIGHT); - return size; - } - }; - scrolled.setExpandHorizontal(true); - scrolled.setExpandVertical(true); - scrolled.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Composite root = new Composite(scrolled, SWT.NONE); + Composite root = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; root.setLayout(layout); + root.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); IPreferenceStore store = getPreferenceStore(); terminalSection = new TerminalAutoApproveSection(root, SWT.NONE); @@ -90,17 +72,9 @@ public Point computeSize(int widthHint, int heightHint, boolean changed) { globalSection = new GlobalAutoApproveSection(root, SWT.NONE); globalSection.loadFromPreferences(store); - scrolled.setContent(root); - scrolled.setMinSize(root.computeSize(SWT.DEFAULT, SWT.DEFAULT)); - // Track width so wrapping labels reflow and only vertical scrolling occurs. - scrolled.addListener(SWT.Resize, e -> { - int width = scrolled.getClientArea().width; - scrolled.setMinSize(root.computeSize(width, SWT.DEFAULT)); - }); - - scrolled.addDisposeListener(e -> unbindMcpConfigService()); + root.addDisposeListener(e -> unbindMcpConfigService()); - return scrolled; + return root; } @Override diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java index 1392a529..0decb161 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/ByokPreferencePage.java @@ -142,7 +142,7 @@ private void refreshPageData() { private void initializeProviderStates() { for (ByokModelProvider provider : ByokModelProvider.values()) { String providerName = provider.getDisplayName(); - if (provider == ByokModelProvider.AZURE) { + if (ByokModelProvider.usesModelLevelCredentials(providerName)) { remotelyLoadedProviders.add(providerName); } } @@ -509,17 +509,17 @@ private void refreshButtonsEnabled() { removeModelButton.setEnabled(selectedModel != null && selectedModel.isCustomModel()); toggleStatusButton.setEnabled(selectedModel != null); reloadButton.setEnabled(true); - // Check if provider is not Azure and has API key + // Check if provider uses provider-level API keys and has one configured. boolean canManageApiKey = false; String providerName = getSelectedProviderName(); if (providerName != null) { - boolean isAzureProvider = ByokModelProvider.isAzure(providerName); + boolean usesModelLevelCredentials = ByokModelProvider.usesModelLevelCredentials(providerName); boolean hasApiKeyForProvider = byProviderApiKeys.containsKey(providerName); - canManageApiKey = !isAzureProvider && hasApiKeyForProvider; + canManageApiKey = !usesModelLevelCredentials && hasApiKeyForProvider; } - // Change API: enabled when provider is not Azure and has API key + // Change API: enabled when provider uses provider-level API keys and has one configured. changeApiButton.setEnabled(canManageApiKey); - // Delete API: enabled when provider is not Azure and has API key + // Delete API: enabled when provider uses provider-level API keys and has one configured. deleteApiButton.setEnabled(canManageApiKey); } @@ -703,7 +703,7 @@ private void onAddModel() { if (providerName != null) { final String finalProviderName = providerName; boolean hasApiKey = byProviderApiKeys.containsKey(providerName); - if (!hasApiKey && !ByokModelProvider.isAzure(providerName)) { + if (!hasApiKey && !ByokModelProvider.usesModelLevelCredentials(providerName)) { AddApiKeyDialog apiKeyDialog = new AddApiKeyDialog(getShell(), providerName, apiKey -> { if (apiKey != null && StringUtils.isNotBlank(apiKey) && byokService != null) { executeAsyncProviderOperation(finalProviderName, byokService.addApiKey(finalProviderName, apiKey), @@ -823,7 +823,7 @@ private void reloadAllProviders() { private void onChangeProviderApi() { String providerName = getSelectedProviderName(); String apiKey = byProviderApiKeys.get(providerName); - if (!ByokModelProvider.isAzure(providerName)) { + if (!ByokModelProvider.usesModelLevelCredentials(providerName)) { final String finalProviderName = providerName; if (!showChangeApiConfirmationDialog(finalProviderName)) { return; @@ -851,7 +851,7 @@ private void onDeleteProviderApi() { final String finalProviderName = providerName; - if (!ByokModelProvider.isAzure(providerName)) { + if (!ByokModelProvider.usesModelLevelCredentials(providerName)) { if (showDeleteApiKeyConfirmationDialog(providerName)) { executeAsyncProviderOperation(finalProviderName, byokService.deleteApiKey(providerName), "Failed to delete API key"); @@ -949,7 +949,7 @@ public boolean hasChildren(Object element) { if (page.loadingProviders.contains(providerName)) { return true; } - if (ByokModelProvider.isAzure(providerName)) { + if (ByokModelProvider.usesModelLevelCredentials(providerName)) { List models = page.byProviderModels.get(providerName); return models != null && !models.isEmpty(); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java index c41975d0..c58c3947 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java @@ -83,7 +83,7 @@ public class McpPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { public static final String ID = "com.microsoft.copilot.eclipse.ui.preferences.McpPreferencePage"; - private static final int GROUP_HEIGHT_HINT = PreferencePageUtils.STANDARD_CONTENT_HEIGHT / 2; + private static final int GROUP_HEIGHT_HINT = 260; private static final Gson GSON = new Gson(); private Group toolsGroup; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java index 7582fd7a..901bc1b9 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java @@ -30,6 +30,7 @@ public class Messages extends NLS { public static String preferences_page_byok_addModel_dialog_title; public static String preferences_page_byok_addModel_modelId; public static String preferences_page_byok_addModel_deploymentUrl; + public static String preferences_page_byok_addModel_baseUrl; public static String preferences_page_byok_addModel_apiKey; public static String preferences_page_byok_addModel_displayName; public static String preferences_page_byok_addModel_supportVision; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/PreferencePageUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/PreferencePageUtils.java index 13ed7ea9..ab676ebf 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/PreferencePageUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/PreferencePageUtils.java @@ -25,15 +25,6 @@ */ public final class PreferencePageUtils { - /** - * Target content height, in pixels, for Copilot preference pages. JFace grows the shared Preferences dialog to - * the tallest page's preferred height and never shrinks it, so each page keeps its scrollable content within - * this height to hold the dialog at a stable size while the user navigates. Pages enforce it differently: - * {@code McpPreferencePage} divides it across two stacked groups; {@code AutoApprovePreferencePage} caps its - * {@code ScrolledComposite} at this height. - */ - public static final int STANDARD_CONTENT_HEIGHT = 520; - // Private constructor to prevent instantiation private PreferencePageUtils() { } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties index f041db59..e4d796b4 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties @@ -18,6 +18,7 @@ preferences_page_byok_removeModel_confirmDialog_message=Do you want to remove th preferences_page_byok_addModel_dialog_title=Add %s Models preferences_page_byok_addModel_modelId=Model ID: * preferences_page_byok_addModel_deploymentUrl=Deployment URL: * +preferences_page_byok_addModel_baseUrl=Base URL: * preferences_page_byok_addModel_apiKey=API Key: * preferences_page_byok_addModel_displayName=Display Name: preferences_page_byok_addModel_supportVision=Support Vision