diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
index 3587438b..d3a2fb01 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
@@ -2856,16 +2856,13 @@ export const SidebarChat = () => {
// resolve mount info
const isResolved = chatThreadsState.allThreads[threadId]?.state.mountedInfo?.mountedIsResolvedRef.current
-
+
useEffect(() => {
if (isResolved) return
chatThreadsState.allThreads[threadId]?.state.mountedInfo?._whenMountedResolver?.({
textAreaRef: textAreaRef,
scrollToBottom: () => scrollToBottom(scrollContainerRef),
})
-
- // Trigger a window resize event to ensure proper layout calculations
- window.dispatchEvent(new Event('resize'))
}, [chatThreadsState, threadId, textAreaRef, scrollContainerRef, isResolved])
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
index 82f47954..990340ed 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
@@ -21,6 +21,7 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS
import { ILLMMessageService } from '../../../../common/sendLLMMessageService.js';
import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js';
import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js';
+import { IVoidCertificateService } from '../../../../common/voidCertificateService.js';
import { IExtensionTransferService } from '../../../../../../../workbench/contrib/void/browser/extensionTransferService.js'
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'
@@ -185,6 +186,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
IVoidSettingsService: accessor.get(IVoidSettingsService),
IEditCodeService: accessor.get(IEditCodeService),
IChatThreadService: accessor.get(IChatThreadService),
+ IVoidCertificateService: accessor.get(IVoidCertificateService),
IInstantiationService: accessor.get(IInstantiationService),
ICodeEditorService: accessor.get(ICodeEditorService),
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
index 9fa3e7cb..118afb19 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
@@ -904,6 +904,127 @@ export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }:
// full settings
+// Certificate Manager Component
+const CertificateManager = () => {
+ const accessor = useAccessor();
+ const voidSettingsService = accessor.get('IVoidSettingsService');
+ const certificateService = accessor.get('IVoidCertificateService');
+ const nativeHostService = accessor.get('INativeHostService');
+ const notificationService = accessor.get('INotificationService');
+ const settingsState = useSettingsState();
+
+ const [certificatePath, setCertificatePath] = useState('');
+ const [isVerifying, setIsVerifying] = useState(false);
+
+ // Get the list of certificates from state as URIs
+ const certificates: URI[] = useMemo(() => {
+ return (settingsState.globalSettings.customRootCertificates || []).map(path => URI.parse(path));
+ }, [settingsState.globalSettings.customRootCertificates]);
+
+ const handleAddCertificate = async () => {
+ if (certificatePath) {
+ setIsVerifying(true);
+ try {
+ const certificateUri = URI.file(certificatePath);
+
+ // Check if certificate already exists
+ if (certificates.some(cert => cert.toString() === certificateUri.toString())) {
+ notificationService.info('Certificate already added');
+ return;
+ }
+
+ // Verify certificate is valid
+ const isValid = await certificateService.verifyCertificatePath(certificateUri);
+ if (!isValid) {
+ notificationService.error(`The certificate file could not be read or is not a valid certificate.`);
+ return;
+ }
+
+ // Add certificate
+ await certificateService.addCustomCertificate(certificateUri);
+ setCertificatePath('');
+ notificationService.info(`Certificate added successfully`);
+ } catch (error) {
+ notificationService.error(`Failed to add certificate: ${error}`);
+ } finally {
+ setIsVerifying(false);
+ }
+ }
+ };
+
+ const handleRemoveCertificate = async (certificateUri: URI) => {
+ try {
+ await certificateService.removeCustomCertificate(certificateUri);
+ notificationService.info(`Certificate removed`);
+ } catch (error) {
+ notificationService.error(`Failed to remove certificate: ${error}`);
+ }
+ };
+
+ const handleBrowse = async () => {
+ try {
+ const result = await nativeHostService.showOpenDialog({
+ properties: ['openFile'], // Use properties array instead of canSelectFiles, canSelectFolders, canSelectMany
+ filters: [
+ { name: 'Certificates', extensions: ['pem', 'crt', 'cert', 'cer'] }
+ ],
+ title: 'Select Root Certificate'
+ });
+
+ if (result && result.filePaths && result.filePaths.length > 0) {
+ setCertificatePath(result.filePaths[0]);
+ }
+ } catch (error) {
+ notificationService.error(`Failed to browse for certificate: ${error}`);
+ }
+ };
+
+ return (
+
+
+
+
+ Browse
+
+
Verifying : "Add"}
+ />
+
+
+ {certificates.length > 0 ? (
+
+
Added Certificates:
+
+ {certificates.map((certUri, index) => (
+
+ {certUri.fsPath}
+
+
+ ))}
+
+
+ ) : (
+
+ No custom certificates added yet
+
+ )}
+
+ );
+};
+
export const Settings = () => {
const isDark = useIsDark()
const accessor = useAccessor()
@@ -1164,6 +1285,26 @@ export const Settings = () => {
{/* General section (formerly GeneralTab) */}
+ {/* Network Settings with Root Certificates */}
+
+
+ Network Settings
+ {`Configure network and certificate settings for API requests.`}
+
+
+
Root Certificates
+
+
Add custom root certificates for HTTPS requests to fix certificate validation errors like "unable to get local issuer certificate".
+
This is useful when using a corporate proxy, gateway, or working behind a firewall that performs SSL inspection.
+
+
+
+
+
+
+
+
+
One-Click Switch
diff --git a/src/vs/workbench/contrib/void/common/voidCertificateService.ts b/src/vs/workbench/contrib/void/common/voidCertificateService.ts
new file mode 100644
index 00000000..b00dd268
--- /dev/null
+++ b/src/vs/workbench/contrib/void/common/voidCertificateService.ts
@@ -0,0 +1,202 @@
+/*--------------------------------------------------------------------------------------
+ * Copyright 2025 Glass Devtools, Inc. All rights reserved.
+ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
+ *--------------------------------------------------------------------------------------*/
+
+import { Disposable } from '../../../../base/common/lifecycle.js';
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
+import { ILogService } from '../../../../platform/log/common/log.js';
+import { IVoidSettingsService } from './voidSettingsService.js';
+import { IRequestService } from '../../../../platform/request/common/request.js';
+import { Emitter } from '../../../../base/common/event.js';
+import { CancellationToken } from '../../../../base/common/cancellation.js';
+import { IRequestContext } from '../../../../base/parts/request/common/request.js';
+import { IFileService } from '../../../../platform/files/common/files.js';
+import { URI } from '../../../../base/common/uri.js';
+
+/**
+ * Service for managing and applying custom certificate paths for secure requests.
+ */
+export interface IVoidCertificateService {
+ readonly _serviceBrand: undefined;
+
+ /**
+ * Get all configured custom certificates as an array of file URIs.
+ */
+ getCustomCertificates(): URI[];
+
+ /**
+ * Add a new custom certificate path.
+ */
+ addCustomCertificate(certificatePath: URI): Promise;
+
+ /**
+ * Remove a custom certificate path.
+ */
+ removeCustomCertificate(certificatePath: URI): Promise;
+
+ /**
+ * Verify that a certificate path exists and is readable.
+ */
+ verifyCertificatePath(certificatePath: URI): Promise;
+
+ /**
+ * Get all certificate contents as a concatenated string for use with HTTPS requests.
+ */
+ getCertificateContents(): Promise;
+}
+
+export const IVoidCertificateService = createDecorator('VoidCertificateService');
+
+/**
+ * Service implementation for managing custom certificates.
+ */
+export class VoidCertificateService extends Disposable implements IVoidCertificateService {
+ readonly _serviceBrand: undefined;
+
+ private readonly _onCertificatesChanged = new Emitter();
+
+ constructor(
+ @IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
+ @ILogService private readonly logService: ILogService,
+ @IRequestService private readonly requestService: IRequestService,
+ @IFileService private readonly fileService: IFileService,
+ ) {
+ super();
+
+ // Override the original request service to include our custom certificates
+ this._overrideRequestService();
+ }
+
+ /**
+ * Get all configured custom certificates
+ */
+ getCustomCertificates(): URI[] {
+ const certificatePaths = this.voidSettingsService.state.globalSettings.customRootCertificates || [];
+ return certificatePaths.map(path => URI.parse(path));
+ }
+
+ /**
+ * Add a new custom certificate path
+ */
+ async addCustomCertificate(certificatePath: URI): Promise {
+ // Verify the certificate path exists
+ const isValid = await this.verifyCertificatePath(certificatePath);
+ if (!isValid) {
+ throw new Error(`Certificate file not found or not readable: ${certificatePath.toString()}`);
+ }
+
+ // Get current certificates
+ const currentCertificates = this.getCustomCertificates();
+
+ // Check if already exists
+ if (currentCertificates.some(cert => cert.toString() === certificatePath.toString())) {
+ return; // Already exists, nothing to do
+ }
+
+ // Add the new certificate path
+ const newCertificates = [...currentCertificates, certificatePath];
+ await this.voidSettingsService.setGlobalSetting('customRootCertificates', newCertificates.map(uri => uri.toString()));
+
+ this._onCertificatesChanged.fire();
+ this.logService.info(`Added custom certificate: ${certificatePath.toString()}`);
+ }
+
+ /**
+ * Remove a custom certificate path
+ */
+ async removeCustomCertificate(certificatePath: URI): Promise {
+ const currentCertificates = this.getCustomCertificates();
+
+ // Remove the certificate
+ const newCertificates = currentCertificates.filter(cert => cert.toString() !== certificatePath.toString());
+
+ // Update settings
+ await this.voidSettingsService.setGlobalSetting('customRootCertificates', newCertificates.map(uri => uri.toString()));
+
+ this._onCertificatesChanged.fire();
+ this.logService.info(`Removed custom certificate: ${certificatePath.toString()}`);
+ }
+
+ /**
+ * Verify a certificate path exists and is readable
+ */
+ async verifyCertificatePath(certificatePath: URI): Promise {
+ try {
+ // Check if file exists and is readable
+ const stats = await this.fileService.stat(certificatePath);
+ if (!stats.isFile) {
+ return false;
+ }
+
+ // Check if we can read the file
+ await this.fileService.readFile(certificatePath);
+ return true;
+ } catch (error) {
+ this.logService.error(`Error verifying certificate path: ${error}`);
+ return false;
+ }
+ }
+
+ /**
+ * Get all certificate contents
+ */
+ async getCertificateContents(): Promise {
+ const certificatePaths = this.getCustomCertificates();
+ const contents: string[] = [];
+
+ for (const certPath of certificatePaths) {
+ try {
+ if (await this.verifyCertificatePath(certPath)) {
+ const fileContent = await this.fileService.readFile(certPath);
+ contents.push(fileContent.value.toString());
+ }
+ } catch (error) {
+ this.logService.error(`Error reading certificate ${certPath.toString()}: ${error}`);
+ }
+ }
+
+ return contents;
+ }
+
+ /**
+ * Override the original request service to include our custom certificates
+ */
+ private _overrideRequestService(): void {
+ // Store the original request method
+ const originalRequest = this.requestService.request.bind(this.requestService);
+
+ // Override the request method to inject our certificates
+ // @ts-ignore - We're monkey patching the request service
+ this.requestService.request = async (options: any, token: CancellationToken): Promise => {
+ // Only add certificates for HTTPS requests
+ if (options.url?.startsWith('https://')) {
+ try {
+ // Load system certificates
+ const systemCerts = await this.requestService.loadCertificates();
+
+ // Load our custom certificates
+ const customCertContents = await this.getCertificateContents();
+
+ if (customCertContents.length > 0) {
+ // Create custom CA option
+ // Documentation in Node.js: https://nodejs.org/api/https.html#https_https_request_options_callback
+ // The 'ca' option can be a string, Buffer, or array of strings/Buffers
+ (options as any).ca = [...systemCerts, ...customCertContents];
+
+ this.logService.debug(`Added ${customCertContents.length} custom certificates to request to ${options.url}`);
+ }
+ } catch (error) {
+ this.logService.error(`Error adding custom certificates: ${error}`);
+ }
+ }
+
+ // Forward to the original request implementation
+ return originalRequest(options, token);
+ };
+ }
+}
+
+// Register the service
+registerSingleton(IVoidCertificateService, VoidCertificateService, InstantiationType.Eager);
\ No newline at end of file
diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts
index a911dfe6..c43319ad 100644
--- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts
+++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts
@@ -434,6 +434,7 @@ export type GlobalSettings = {
showInlineSuggestions: boolean;
includeToolLintErrors: boolean;
isOnboardingComplete: boolean;
+ customRootCertificates: string[]; // Paths to custom root certificates
}
export const defaultGlobalSettings: GlobalSettings = {
@@ -447,6 +448,7 @@ export const defaultGlobalSettings: GlobalSettings = {
showInlineSuggestions: true,
includeToolLintErrors: true,
isOnboardingComplete: false,
+ customRootCertificates: [],
}
export type GlobalSettingName = keyof GlobalSettings