Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f80c653d5 |
Generated
+14
-32
@@ -299,14 +299,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@azure/core-auth": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz",
|
||||
"integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz",
|
||||
"integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-util": "^1.11.0",
|
||||
"@azure/core-util": "^1.1.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -449,18 +448,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.20.0.tgz",
|
||||
"integrity": "sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g==",
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.0.tgz",
|
||||
"integrity": "sha512-CeuTvsXxCUmEuxH5g/aceuSl6w2EugvNHKAtKKVdiX915EjJJxAwfzNNWZreNnbxHZ2fi0zaM6wwS23x2JVqSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-auth": "^1.8.0",
|
||||
"@azure/core-auth": "^1.4.0",
|
||||
"@azure/core-tracing": "^1.0.1",
|
||||
"@azure/core-util": "^1.11.0",
|
||||
"@azure/core-util": "^1.9.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"@typespec/ts-http-runtime": "^0.2.2",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -480,14 +479,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-util": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.12.0.tgz",
|
||||
"integrity": "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==",
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz",
|
||||
"integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@typespec/ts-http-runtime": "^0.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4260,21 +4257,6 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typespec/ts-http-runtime": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.2.tgz",
|
||||
"integrity": "sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/deviceid": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/deviceid/-/deviceid-0.1.1.tgz",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nameShort": "Void",
|
||||
"nameLong": "Void",
|
||||
"voidVersion": "1.3.10",
|
||||
"voidRelease": "0034",
|
||||
"voidVersion": "1.3.7",
|
||||
"voidRelease": "0031",
|
||||
"applicationName": "void",
|
||||
"dataFolderName": ".void-editor",
|
||||
"win32MutexName": "voideditor",
|
||||
|
||||
@@ -2856,13 +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),
|
||||
})
|
||||
|
||||
}, [chatThreadsState, threadId, textAreaRef, scrollContainerRef, isResolved])
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -17,7 +17,7 @@ import { os } from '../../../../common/helpers/systemInfo.js'
|
||||
import { IconLoading } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js'
|
||||
import Severity from '../../../../../../../base/common/severity.js'
|
||||
import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../../../common/modelCapabilities.js';
|
||||
import { getModelCapabilities, ModelOverrides } from '../../../../common/modelCapabilities.js';
|
||||
import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js';
|
||||
|
||||
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
||||
@@ -185,11 +185,6 @@ const ConfirmButton = ({ children, onConfirm, className }: { children: React.Rea
|
||||
};
|
||||
|
||||
// ---------------- Simplified Model Settings Dialog ------------------
|
||||
|
||||
// keys of ModelOverrides we allow the user to override
|
||||
|
||||
|
||||
|
||||
// This new dialog replaces the verbose UI with a single JSON override box.
|
||||
const SimpleModelSettingsDialog = ({
|
||||
isOpen,
|
||||
@@ -213,26 +208,39 @@ const SimpleModelSettingsDialog = ({
|
||||
const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] ?? undefined;
|
||||
const { recognizedModelName, isUnrecognizedModel } = defaultModelCapabilities
|
||||
|
||||
// keys of ModelOverrides we allow the user to override
|
||||
const allowedKeys: (string & (keyof ModelOverrides))[] = [
|
||||
'contextWindow',
|
||||
'reservedOutputTokenSpace',
|
||||
'supportsSystemMessage',
|
||||
'specialToolFormat',
|
||||
'supportsFIM',
|
||||
'reasoningCapabilities',
|
||||
];
|
||||
|
||||
// Create the placeholder with the default values for allowed keys
|
||||
const partialDefaults: Partial<ModelOverrides> = {};
|
||||
for (const k of modelOverrideKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; }
|
||||
for (const k of allowedKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; }
|
||||
const placeholder = JSON.stringify(partialDefaults, null, 2);
|
||||
|
||||
const [overrideEnabled, setOverrideEnabled] = useState<boolean>(() => !!currentOverrides);
|
||||
const [jsonText, setJsonText] = useState<string>(() => currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder);
|
||||
|
||||
const [readOnlyHeight, setReadOnlyHeight] = useState<number | undefined>(undefined);
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
|
||||
|
||||
// reset when dialog toggles
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const cur = settingsState.overridesOfModel?.[providerName]?.[modelName];
|
||||
setOverrideEnabled(!!cur);
|
||||
// If there are overrides, show them; otherwise use default values
|
||||
setJsonText(cur ? JSON.stringify(cur, null, 2) : placeholder);
|
||||
setErrorMsg(null);
|
||||
}, [isOpen, providerName, modelName, settingsState.overridesOfModel, placeholder]);
|
||||
|
||||
const onSave = async () => {
|
||||
|
||||
// if disabled override, reset overrides
|
||||
if (!overrideEnabled) {
|
||||
await settingsStateService.setOverridesOfModel(providerName, modelName, undefined);
|
||||
@@ -243,10 +251,9 @@ const SimpleModelSettingsDialog = ({
|
||||
// enabled overrides
|
||||
// parse json
|
||||
let parsedInput: Record<string, unknown>
|
||||
|
||||
if (textAreaRef.current?.value) {
|
||||
if (jsonText.trim()) {
|
||||
try {
|
||||
parsedInput = JSON.parse(textAreaRef.current.value);
|
||||
parsedInput = JSON.parse(jsonText);
|
||||
} catch (e) {
|
||||
setErrorMsg('Invalid JSON');
|
||||
return;
|
||||
@@ -258,10 +265,10 @@ const SimpleModelSettingsDialog = ({
|
||||
|
||||
// only keep allowed keys
|
||||
const cleaned: Partial<ModelOverrides> = {};
|
||||
for (const k of modelOverrideKeys) {
|
||||
for (const k of allowedKeys) {
|
||||
if (!(k in parsedInput)) continue
|
||||
const isEmpty = parsedInput[k] === '' || parsedInput[k] === null || parsedInput[k] === undefined;
|
||||
if (!isEmpty) {
|
||||
if (!isEmpty && (k in partialDefaults)) {
|
||||
cleaned[k] = parsedInput[k] as any;
|
||||
}
|
||||
}
|
||||
@@ -326,11 +333,10 @@ const SimpleModelSettingsDialog = ({
|
||||
</div>}
|
||||
|
||||
<textarea
|
||||
key={overrideEnabled + ''}
|
||||
ref={textAreaRef}
|
||||
className={`w-full min-h-[200px] p-2 rounded-sm border border-void-border-2 bg-void-bg-2 resize-none font-mono text-sm ${!overrideEnabled ? 'text-void-fg-3' : ''}`}
|
||||
defaultValue={overrideEnabled && currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder}
|
||||
value={overrideEnabled ? jsonText : placeholder}
|
||||
placeholder={placeholder}
|
||||
onChange={overrideEnabled ? (e) => setJsonText(e.target.value) : undefined}
|
||||
readOnly={!overrideEnabled}
|
||||
/>
|
||||
{errorMsg && (
|
||||
@@ -898,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 (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<VoidSimpleInputBox
|
||||
value={certificatePath}
|
||||
onChangeValue={setCertificatePath}
|
||||
placeholder="Path to certificate (.pem, .crt, .cert, .cer)"
|
||||
compact={true}
|
||||
className="flex-grow"
|
||||
/>
|
||||
<VoidButtonBgDarken onClick={handleBrowse} className="px-3 py-1">
|
||||
Browse
|
||||
</VoidButtonBgDarken>
|
||||
<AddButton
|
||||
disabled={!certificatePath || isVerifying}
|
||||
onClick={handleAddCertificate}
|
||||
text={isVerifying ? <span className="flex items-center gap-1">Verifying <Loader2 className="size-3 animate-spin" /></span> : "Add"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{certificates.length > 0 ? (
|
||||
<div className="mt-2">
|
||||
<h4 className="text-void-fg-3 text-sm mb-1">Added Certificates:</h4>
|
||||
<div className="flex flex-col gap-1">
|
||||
{certificates.map((certUri, index) => (
|
||||
<div key={certUri.toString()} className="flex justify-between items-center py-1 px-2 bg-void-bg-2 rounded">
|
||||
<span className="text-sm truncate max-w-md" title={certUri.fsPath}>{certUri.fsPath}</span>
|
||||
<button
|
||||
onClick={() => handleRemoveCertificate(certUri)}
|
||||
className="text-void-fg-3 hover:text-void-fg-1"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-void-fg-3 text-sm italic mt-1">
|
||||
No custom certificates added yet
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Settings = () => {
|
||||
const isDark = useIsDark()
|
||||
const accessor = useAccessor()
|
||||
@@ -1158,6 +1285,26 @@ export const Settings = () => {
|
||||
|
||||
|
||||
{/* General section (formerly GeneralTab) */}
|
||||
{/* Network Settings with Root Certificates */}
|
||||
<div className='mt-12'>
|
||||
<ErrorBoundary>
|
||||
<h2 className='text-3xl mb-2'>Network Settings</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Configure network and certificate settings for API requests.`}</h4>
|
||||
|
||||
<div className='mb-4'>
|
||||
<h3 className='text-base mb-2'>Root Certificates</h3>
|
||||
<div className='text-sm italic text-void-fg-3 mb-2'>
|
||||
<p>Add custom root certificates for HTTPS requests to fix certificate validation errors like "unable to get local issuer certificate".</p>
|
||||
<p className="mt-1">This is useful when using a corporate proxy, gateway, or working behind a firewall that performs SSL inspection.</p>
|
||||
</div>
|
||||
|
||||
<div className='max-w-xl'>
|
||||
<CertificateManager />
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
<div className='mt-12'>
|
||||
<ErrorBoundary>
|
||||
<h2 className='text-3xl mb-2 mt-12'>One-Click Switch</h2>
|
||||
|
||||
@@ -8,7 +8,7 @@ import Severity from '../../../../base/common/severity.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { localize2 } from '../../../../nls.js';
|
||||
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
||||
import { INotificationActions, INotificationHandle, INotificationService } from '../../../../platform/notification/common/notification.js';
|
||||
import { INotificationActions, INotificationService } from '../../../../platform/notification/common/notification.js';
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { IVoidUpdateService } from '../common/voidUpdateService.js';
|
||||
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
@@ -20,7 +20,7 @@ import { IAction } from '../../../../base/common/actions.js';
|
||||
|
||||
|
||||
|
||||
const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService): INotificationHandle => {
|
||||
const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService) => {
|
||||
const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!'
|
||||
|
||||
let actions: INotificationActions | undefined
|
||||
@@ -119,21 +119,18 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
|
||||
progress: actions ? { worked: 0, total: 100 } : undefined,
|
||||
actions: actions,
|
||||
})
|
||||
|
||||
return notifController
|
||||
// const d = notifController.onDidClose(() => {
|
||||
// notifyYesUpdate(notifService, res)
|
||||
// d.dispose()
|
||||
// })
|
||||
}
|
||||
const notifyErrChecking = (notifService: INotificationService): INotificationHandle => {
|
||||
const notifyErrChecking = (notifService: INotificationService) => {
|
||||
const message = `Void Error: There was an error checking for updates. If this persists, please get in touch or reinstall Void [here](https://voideditor.com/download-beta)!`
|
||||
const notifController = notifService.notify({
|
||||
notifService.notify({
|
||||
severity: Severity.Info,
|
||||
message: message,
|
||||
sticky: true,
|
||||
})
|
||||
return notifController
|
||||
}
|
||||
|
||||
|
||||
@@ -143,35 +140,30 @@ const performVoidCheck = async (
|
||||
voidUpdateService: IVoidUpdateService,
|
||||
metricsService: IMetricsService,
|
||||
updateService: IUpdateService,
|
||||
): Promise<INotificationHandle | null> => {
|
||||
) => {
|
||||
|
||||
const metricsTag = explicit ? 'Manual' : 'Auto'
|
||||
|
||||
metricsService.capture(`Void Update ${metricsTag}: Checking...`, {})
|
||||
const res = await voidUpdateService.check(explicit)
|
||||
if (!res) {
|
||||
const notifController = notifyErrChecking(notifService);
|
||||
notifyErrChecking(notifService);
|
||||
metricsService.capture(`Void Update ${metricsTag}: Error`, { res })
|
||||
return notifController
|
||||
}
|
||||
else {
|
||||
if (res.message) {
|
||||
const notifController = notifyUpdate(res, notifService, updateService)
|
||||
notifyUpdate(res, notifService, updateService)
|
||||
metricsService.capture(`Void Update ${metricsTag}: Yes`, { res })
|
||||
return notifController
|
||||
}
|
||||
else {
|
||||
metricsService.capture(`Void Update ${metricsTag}: No`, { res })
|
||||
return null
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Action
|
||||
let lastNotifController: INotificationHandle | null = null
|
||||
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -185,15 +177,7 @@ registerAction2(class extends Action2 {
|
||||
const notifService = accessor.get(INotificationService)
|
||||
const metricsService = accessor.get(IMetricsService)
|
||||
const updateService = accessor.get(IUpdateService)
|
||||
|
||||
const currNotifController = lastNotifController
|
||||
|
||||
const newController = await performVoidCheck(true, notifService, voidUpdateService, metricsService, updateService)
|
||||
|
||||
if (newController) {
|
||||
currNotifController?.close()
|
||||
lastNotifController = newController
|
||||
}
|
||||
performVoidCheck(true, notifService, voidUpdateService, metricsService, updateService)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ export const defaultModelsOfProvider = {
|
||||
'claude-3-opus-latest',
|
||||
],
|
||||
xAI: [ // https://docs.x.ai/docs/models?cluster=us-east-1
|
||||
'grok-2',
|
||||
'grok-3',
|
||||
'grok-2-latest',
|
||||
'grok-3-latest',
|
||||
],
|
||||
gemini: [ // https://ai.google.dev/gemini-api/docs/models/gemini
|
||||
'gemini-2.5-pro-exp-03-25',
|
||||
@@ -188,20 +188,14 @@ export type VoidStaticModelInfo = { // not stateful
|
||||
// if you change the above type, remember to update the Settings link
|
||||
|
||||
|
||||
|
||||
export const modelOverrideKeys = [
|
||||
'contextWindow',
|
||||
'reservedOutputTokenSpace',
|
||||
'supportsSystemMessage',
|
||||
'specialToolFormat',
|
||||
'supportsFIM',
|
||||
'reasoningCapabilities',
|
||||
'additionalOpenAIPayload'
|
||||
] as const
|
||||
|
||||
export type ModelOverrides = Pick<
|
||||
VoidStaticModelInfo,
|
||||
(typeof modelOverrideKeys)[number]
|
||||
export type ModelOverrides = Pick<VoidStaticModelInfo,
|
||||
| 'contextWindow'
|
||||
| 'reservedOutputTokenSpace'
|
||||
| 'specialToolFormat'
|
||||
| 'supportsSystemMessage'
|
||||
| 'supportsFIM'
|
||||
| 'reasoningCapabilities'
|
||||
| 'additionalOpenAIPayload'
|
||||
>
|
||||
|
||||
|
||||
@@ -377,28 +371,22 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
|
||||
: VoidStaticModelInfo & { modelName: string, recognizedModelName: string } => {
|
||||
|
||||
const opts = obj[recognizedModelName]
|
||||
const supportsSystemMessage = opts.supportsSystemMessage === 'separated'
|
||||
? 'system-role'
|
||||
: opts.supportsSystemMessage
|
||||
|
||||
return {
|
||||
recognizedModelName,
|
||||
modelName,
|
||||
...opts,
|
||||
supportsSystemMessage: supportsSystemMessage,
|
||||
supportsSystemMessage: opts.supportsSystemMessage ? 'system-role' : false,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
...fallbackKnownValues
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) return toFallback(geminiModelOptions, 'gemini-2.5-pro-exp-03-25')
|
||||
|
||||
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions, 'claude-3-5-sonnet-20241022')
|
||||
if (lower.includes('claude')) return toFallback(anthropicModelOptions, 'claude-3-7-sonnet-20250219')
|
||||
|
||||
if (lower.includes('grok2') || lower.includes('grok2')) return toFallback(xAIModelOptions, 'grok-2')
|
||||
if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-3')
|
||||
if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-2')
|
||||
|
||||
if (lower.includes('deepseek-r1') || lower.includes('deepseek-reasoner')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekR1')
|
||||
if (lower.includes('deepseek') && lower.includes('v2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV2')
|
||||
@@ -642,16 +630,6 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
|
||||
// https://platform.openai.com/docs/guides/reasoning?api-mode=chat
|
||||
const openAICompatIncludeInPayloadReasoning = (reasoningInfo: SendableReasoningInfo) => {
|
||||
if (!reasoningInfo?.isReasoningEnabled) return null
|
||||
if (reasoningInfo.type === 'effort_slider_value') {
|
||||
return { reasoning_effort: reasoningInfo.reasoningEffort }
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
const openAISettings: VoidStaticProviderInfo = {
|
||||
modelOptions: openAIModelOptions,
|
||||
modelOptionsFallback: (modelName) => {
|
||||
@@ -664,7 +642,17 @@ const openAISettings: VoidStaticProviderInfo = {
|
||||
return null
|
||||
},
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
input: {
|
||||
// https://platform.openai.com/docs/guides/reasoning?api-mode=chat
|
||||
includeInPayload: (reasoningInfo) => {
|
||||
if (!reasoningInfo?.isReasoningEnabled) return null
|
||||
|
||||
if (reasoningInfo.type === 'effort_slider_value') {
|
||||
return { reasoning_effort: reasoningInfo.reasoningEffort }
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -679,7 +667,6 @@ const xAIModelOptions = {
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'openai-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'grok-3': {
|
||||
@@ -689,7 +676,6 @@ const xAIModelOptions = {
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'openai-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'grok-3-fast': {
|
||||
@@ -699,7 +685,6 @@ const xAIModelOptions = {
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'openai-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
// only mini supports thinking
|
||||
@@ -710,7 +695,6 @@ const xAIModelOptions = {
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'openai-style',
|
||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } },
|
||||
},
|
||||
'grok-3-mini-fast': {
|
||||
@@ -720,7 +704,6 @@ const xAIModelOptions = {
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'openai-style',
|
||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } },
|
||||
},
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
@@ -731,15 +714,11 @@ const xAISettings: VoidStaticProviderInfo = {
|
||||
const lower = modelName.toLowerCase()
|
||||
let fallbackName: keyof typeof xAIModelOptions | null = null
|
||||
if (lower.includes('grok-2')) fallbackName = 'grok-2'
|
||||
if (lower.includes('grok-3')) fallbackName = 'grok-3'
|
||||
if (lower.includes('grok')) fallbackName = 'grok-3'
|
||||
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...xAIModelOptions[fallbackName] }
|
||||
return null
|
||||
},
|
||||
// same implementation as openai
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
},
|
||||
providerReasoningIOSettings: openAISettings.providerReasoningIOSettings,
|
||||
}
|
||||
|
||||
|
||||
@@ -858,7 +837,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||
|
||||
const geminiSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: geminiModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
||||
|
||||
@@ -884,12 +863,11 @@ const deepseekModelOptions = {
|
||||
|
||||
const deepseekSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: deepseekModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
providerReasoningIOSettings: {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://api-docs.deepseek.com/guides/reasoning_model
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
output: { nameOfFieldInDelta: 'reasoning_content' },
|
||||
},
|
||||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
||||
|
||||
@@ -947,9 +925,6 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici
|
||||
const mistralSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: mistralModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -993,13 +968,11 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
|
||||
},
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
const groqSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: groqModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
providerReasoningIOSettings: {
|
||||
// Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning
|
||||
input: {
|
||||
includeInPayload: (reasoningInfo) => {
|
||||
if (!reasoningInfo?.isReasoningEnabled) return null
|
||||
|
||||
if (reasoningInfo.type === 'budget_slider_value') {
|
||||
return { reasoning_format: 'parsed' }
|
||||
}
|
||||
@@ -1007,7 +980,9 @@ const groqSettings: VoidStaticProviderInfo = {
|
||||
}
|
||||
},
|
||||
output: { nameOfFieldInDelta: 'reasoning' },
|
||||
},
|
||||
}, // Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning
|
||||
modelOptions: groqModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
||||
|
||||
@@ -1016,10 +991,7 @@ const googleVertexModelOptions = {
|
||||
} as const satisfies Record<string, VoidStaticModelInfo>
|
||||
const googleVertexSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: googleVertexModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
},
|
||||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
||||
// ---------------- MICROSOFT AZURE ----------------
|
||||
@@ -1027,10 +999,7 @@ const microsoftAzureModelOptions = {
|
||||
} as const satisfies Record<string, VoidStaticModelInfo>
|
||||
const microsoftAzureSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: microsoftAzureModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null },
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
},
|
||||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
||||
|
||||
@@ -1106,50 +1075,35 @@ export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq',
|
||||
|
||||
|
||||
const vLLMSettings: VoidStaticProviderInfo = {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://docs.vllm.ai/en/stable/features/reasoning_outputs.html#streaming-chat-completions
|
||||
providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' }, },
|
||||
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
|
||||
modelOptions: {},
|
||||
providerReasoningIOSettings: {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://docs.vllm.ai/en/stable/features/reasoning_outputs.html#streaming-chat-completions
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
output: { nameOfFieldInDelta: 'reasoning_content' },
|
||||
},
|
||||
modelOptions: {}, // TODO
|
||||
}
|
||||
|
||||
const lmStudioSettings: VoidStaticProviderInfo = {
|
||||
providerReasoningIOSettings: { output: { needsManualParse: true }, },
|
||||
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' }, contextWindow: 4_096 }),
|
||||
modelOptions: {},
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
output: { needsManualParse: true },
|
||||
},
|
||||
modelOptions: {}, // TODO
|
||||
}
|
||||
|
||||
const ollamaSettings: VoidStaticProviderInfo = {
|
||||
// reasoning: we need to filter out reasoning <think> tags manually
|
||||
providerReasoningIOSettings: { output: { needsManualParse: true }, },
|
||||
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
|
||||
modelOptions: ollamaModelOptions,
|
||||
providerReasoningIOSettings: {
|
||||
// reasoning: we need to filter out reasoning <think> tags manually
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
output: { needsManualParse: true },
|
||||
},
|
||||
}
|
||||
|
||||
const openaiCompatible: VoidStaticProviderInfo = {
|
||||
// reasoning: we have no idea what endpoint they used, so we can't consistently parse out reasoning
|
||||
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName),
|
||||
modelOptions: {},
|
||||
providerReasoningIOSettings: {
|
||||
// reasoning: we have no idea what endpoint they used, so we can't consistently parse out reasoning
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
},
|
||||
}
|
||||
|
||||
const liteLLMSettings: VoidStaticProviderInfo = { // https://docs.litellm.ai/docs/reasoning_content
|
||||
providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' } },
|
||||
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
|
||||
modelOptions: {},
|
||||
providerReasoningIOSettings: {
|
||||
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
|
||||
output: { nameOfFieldInDelta: 'reasoning_content' },
|
||||
},
|
||||
modelOptions: {}, // TODO
|
||||
}
|
||||
|
||||
|
||||
@@ -1274,18 +1228,8 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
const openRouterSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: openRouterModelOptions_assumingOpenAICompat,
|
||||
// TODO!!! send a query to openrouter to get the price, etc.
|
||||
modelOptionsFallback: (modelName) => {
|
||||
const res = extensiveModelOptionsFallback(modelName)
|
||||
// openRouter does not support gemini-style, use openai-style instead
|
||||
if (res?.specialToolFormat === 'gemini-style') {
|
||||
res.specialToolFormat = 'openai-style'
|
||||
}
|
||||
return res
|
||||
},
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models
|
||||
providerReasoningIOSettings: {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models
|
||||
input: {
|
||||
// https://openrouter.ai/docs/use-cases/reasoning-tokens
|
||||
includeInPayload: (reasoningInfo) => {
|
||||
@@ -1309,6 +1253,16 @@ const openRouterSettings: VoidStaticProviderInfo = {
|
||||
},
|
||||
output: { nameOfFieldInDelta: 'reasoning' },
|
||||
},
|
||||
modelOptions: openRouterModelOptions_assumingOpenAICompat,
|
||||
// TODO!!! send a query to openrouter to get the price, etc.
|
||||
modelOptionsFallback: (modelName) => {
|
||||
const res = extensiveModelOptionsFallback(modelName)
|
||||
// openRouter does not support gemini-style, use openai-style instead
|
||||
if (res?.specialToolFormat === 'gemini-style') {
|
||||
res.specialToolFormat = 'openai-style'
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<void>;
|
||||
|
||||
/**
|
||||
* Remove a custom certificate path.
|
||||
*/
|
||||
removeCustomCertificate(certificatePath: URI): Promise<void>;
|
||||
|
||||
/**
|
||||
* Verify that a certificate path exists and is readable.
|
||||
*/
|
||||
verifyCertificatePath(certificatePath: URI): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Get all certificate contents as a concatenated string for use with HTTPS requests.
|
||||
*/
|
||||
getCertificateContents(): Promise<string[]>;
|
||||
}
|
||||
|
||||
export const IVoidCertificateService = createDecorator<IVoidCertificateService>('VoidCertificateService');
|
||||
|
||||
/**
|
||||
* Service implementation for managing custom certificates.
|
||||
*/
|
||||
export class VoidCertificateService extends Disposable implements IVoidCertificateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onCertificatesChanged = new Emitter<void>();
|
||||
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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<boolean> {
|
||||
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<string[]> {
|
||||
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<IRequestContext> => {
|
||||
// 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);
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/* eslint-disable */
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import { Ollama } from 'ollama';
|
||||
import OpenAI, { ClientOptions, AzureOpenAI } from 'openai';
|
||||
import OpenAI, { ClientOptions } from 'openai';
|
||||
import { MistralCore } from '@mistralai/mistralai/core.js';
|
||||
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
||||
import { Tool as GeminiTool, FunctionDeclaration, GoogleGenAI, ThinkingConfig, Schema, Type } from '@google/genai';
|
||||
@@ -114,12 +114,9 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
|
||||
}
|
||||
else if (providerName === 'microsoftAzure') {
|
||||
// https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions?view=rest-aifoundry-model-inference-2024-05-01-preview&tabs=HTTP
|
||||
// https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai
|
||||
const thisConfig = settingsOfProvider[providerName]
|
||||
const endpoint = `https://${thisConfig.project}.openai.azure.com/`;
|
||||
const apiVersion = thisConfig.azureApiVersion ?? '2024-04-01-preview';
|
||||
const options = { endpoint, apiKey: thisConfig.apiKey, apiVersion };
|
||||
return new AzureOpenAI({ ...options, ...commonPayloadOpts });
|
||||
const baseURL = `https://${thisConfig.project}.services.ai.azure.com/api/models/chat/completions?api-version=${thisConfig.azureApiVersion}`
|
||||
return new OpenAI({ baseURL: baseURL, apiKey: thisConfig.apiKey, ...commonPayloadOpts })
|
||||
}
|
||||
|
||||
else if (providerName === 'deepseek') {
|
||||
@@ -266,10 +263,6 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
|
||||
|
||||
// instance
|
||||
const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
|
||||
if (providerName === 'microsoftAzure') {
|
||||
// Required to select the model
|
||||
(openai as AzureOpenAI).deploymentName = modelName;
|
||||
}
|
||||
const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: modelName,
|
||||
messages: messages as any,
|
||||
|
||||
Reference in New Issue
Block a user