From 278c52bd353a3c690f683c4bbb0bd4d2de0194cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Commaret?= Date: Mon, 13 Jan 2025 22:48:43 +0100 Subject: [PATCH] Merging everything here --- .gitignore | 1 + package-lock.json | 24 +++- package.json | 4 +- src/package-lock.json | 62 +++++++++ .../platform/void/common/voidSettingsTypes.ts | 42 +++++- .../void/electron-main/llmMessage/mistral.ts | 123 ++++++++++++++++++ .../llmMessage/sendLLMMessage.ts | 4 + void | 1 + 8 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 src/package-lock.json create mode 100644 src/vs/platform/void/electron-main/llmMessage/mistral.ts create mode 160000 void diff --git a/.gitignore b/.gitignore index 5daf304c..fcdb9b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ product.overrides.json *.snap.actual .vscode-test .tmp/ +.tool-versions diff --git a/package-lock.json b/package-lock.json index 91937c1c..510d7228 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@google/generative-ai": "^0.21.0", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", + "@mistralai/mistralai": "^1.3.5", "@parcel/watcher": "2.1.0", "@rrweb/record": "^2.0.0-alpha.17", "@rrweb/types": "^2.0.0-alpha.17", @@ -65,7 +66,8 @@ "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.1.0", "yauzl": "^3.0.0", - "yazl": "^2.4.3" + "yazl": "^2.4.3", + "zod": "^3.24.1" }, "devDependencies": { "@playwright/test": "^1.46.1", @@ -2366,6 +2368,7 @@ "exenv-es6": "^1.1.1" } }, +<<<<<<< HEAD "node_modules/@next/env": { "version": "15.1.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.4.tgz", @@ -2507,6 +2510,16 @@ ], "engines": { "node": ">= 10" +======= + "node_modules/@mistralai/mistralai": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.3.5.tgz", + "integrity": "sha512-yC91oJ5ScEPqbXmv3mJTwTFgu/ZtsYoOPOhaVXSsy6x4zXTqTI57yEC1flC9uiA8GpG/yhpn2BBUXF95+U9Blw==", + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "zod": ">= 3" +>>>>>>> fork/feat-mistral-new } }, "node_modules/@nodelib/fs.scandir": { @@ -24104,6 +24117,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 9833d8a9..ebd91306 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "@google/generative-ai": "^0.21.0", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", + "@mistralai/mistralai": "^1.3.5", "@parcel/watcher": "2.1.0", "@rrweb/record": "^2.0.0-alpha.17", "@rrweb/types": "^2.0.0-alpha.17", @@ -132,7 +133,8 @@ "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.1.0", "yauzl": "^3.0.0", - "yazl": "^2.4.3" + "yazl": "^2.4.3", + "zod": "^3.24.1" }, "devDependencies": { "@playwright/test": "^1.46.1", diff --git a/src/package-lock.json b/src/package-lock.json new file mode 100644 index 00000000..3b02711d --- /dev/null +++ b/src/package-lock.json @@ -0,0 +1,62 @@ +{ + "name": "src", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@mistralai/mistralai": "^1.3.5", + "zod": "^3.23.8" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.3.5.tgz", + "integrity": "sha512-yC91oJ5ScEPqbXmv3mJTwTFgu/ZtsYoOPOhaVXSsy6x4zXTqTI57yEC1flC9uiA8GpG/yhpn2BBUXF95+U9Blw==", + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "zod": ">= 3" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT", + "peer": true + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/vs/platform/void/common/voidSettingsTypes.ts b/src/vs/platform/void/common/voidSettingsTypes.ts index 2b068780..acc81f02 100644 --- a/src/vs/platform/void/common/voidSettingsTypes.ts +++ b/src/vs/platform/void/common/voidSettingsTypes.ts @@ -94,7 +94,18 @@ export const defaultGeminiModels = modelInfoOfDefaultNames([ 'gemini-1.0-pro' ]) - +export const defaultMistralModels = modelInfoOfDefaultNames([ + "open-codestral-mamba", + "open-mistral-nemo", + "pixtral-12b-2409", + "mistral-large-latest", + "pixtral-large-latest", + "ministral-3b-latest", + "ministral-8b-latest", + "mistral-small-latest", + "codestral-latest", + "mistral-embed" +]) // export const parseMaxTokensStr = (maxTokensStr: string) => { // // parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN @@ -144,6 +155,9 @@ export const defaultProviderSettings = { apiKey: '', }, groq: { + apiKey: '', + }, + mistral: { apiKey: '' } } as const @@ -224,6 +238,11 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn title: 'Groq', } } + else if (providerName === 'mistral') { + return { + title: 'Mistral', + } + } throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`) } @@ -242,16 +261,18 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key providerName === 'gemini' ? 'key...' : providerName === 'groq' ? 'gsk_key...' : - providerName === 'openAICompatible' ? 'sk-key...' : - '(never)', + providerName === 'mistral' ? 'api-key...' : + providerName === 'openAICompatible' ? 'sk-key...' : + '(never)', subTextMd: providerName === 'anthropic' ? 'Get your [API Key here](https://console.anthropic.com/settings/keys).' : providerName === 'openAI' ? 'Get your [API Key here](https://platform.openai.com/api-keys).' : providerName === 'openRouter' ? 'Get your [API Key here](https://openrouter.ai/settings/keys).' : providerName === 'gemini' ? 'Get your [API Key here](https://aistudio.google.com/apikey).' : providerName === 'groq' ? 'Get your [API Key here](https://console.groq.com/keys).' : - providerName === 'openAICompatible' ? 'Add any OpenAI-Compatible endpoint.' : - undefined, + providerName === 'mistral' ? 'Get your [API Key here](https://console.mistral.ai/api-keys/).' : + providerName === 'openAICompatible' ? undefined : + undefined, } } else if (settingName === 'endpoint') { @@ -293,6 +314,8 @@ const defaultCustomSettings: Record = { endpoint: undefined, } + + export const voidInitModelOptions = { anthropic: { models: defaultAnthropicModels, @@ -315,6 +338,9 @@ export const voidInitModelOptions = { groq: { models: defaultGroqModels, }, + mistral: { + models: defaultMistralModels, + } } @@ -362,6 +388,12 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...voidInitModelOptions.openAICompatible, _enabled: undefined, }, + mistral: { + ...defaultCustomSettings, + ...defaultProviderSettings.mistral, + ...voidInitModelOptions.mistral, + _enabled: undefined, + } } diff --git a/src/vs/platform/void/electron-main/llmMessage/mistral.ts b/src/vs/platform/void/electron-main/llmMessage/mistral.ts new file mode 100644 index 00000000..ca32fd49 --- /dev/null +++ b/src/vs/platform/void/electron-main/llmMessage/mistral.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Glass Devtools, Inc. All rights reserved. + * Mistral implementation by Jérôme Commaret (https://github.com/jcommaret) + * Void Editor additions licensed under the AGPL 3.0 License. + *--------------------------------------------------------------------------------------------*/ + +import { Mistral } from '@mistralai/mistralai'; +import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js'; + +interface MistralMessage { + role: 'user' | 'assistant'; + content: string; +} + +interface MistralChunk { + data: { + id: string; + object: string; + created: number; + model: string; + choices: Array<{ + index: number; + delta: { + content?: string; + role?: string; + }; + finishReason: string | null; + }>; + }; +} + +// Mistral +export const sendMistralMsg: _InternalSendLLMMessageFnType = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => { + let fullText = ''; + let aborted = false; + + const thisConfig = settingsOfProvider.mistral; + + if (!thisConfig.apiKey) { + onError({ message: 'Mistral API key not configured.', fullError: new Error('No API key') }); + return; + } + + const mistral = new Mistral({ + apiKey: thisConfig.apiKey + }); + + // Define the aborter before staring the stream + _setAborter(() => { + aborted = true; + }); + + try { + // Check if there are messages to process + if (!messages || messages.length === 0) { + onError({ message: 'No messages to process.', fullError: new Error('No messages provided') }); + return; + } + + // Convert messages for Mistral + const mistralMessages = messages + .filter(msg => msg.role !== 'system') // Ignore system messages + .map(msg => ({ + role: msg.role === 'assistant' ? 'assistant' : 'user', + content: msg.content.trim() + })) as MistralMessage[]; + + // Ensure there is at least one message + if (mistralMessages.length === 0) { + onError({ message: 'No valid messages to send.', fullError: new Error('No valid messages') }); + return; + } + + // Ensure the last message is from the user + if (mistralMessages[mistralMessages.length - 1].role === 'assistant') { + mistralMessages.push({ + role: 'user', + content: 'Continue.' + }); + } + + const stream = await mistral.chat.stream({ + model: modelName, + messages: mistralMessages, + temperature: 0.7, + maxTokens: 2048 + }); + + for await (const chunk of stream) { + // Check if the request has been aborted + if (aborted) { + return; + } + + if (typeof chunk === 'object' && chunk && 'data' in chunk) { + const { data } = chunk as MistralChunk; + if (data.choices?.[0]?.delta?.content) { + const newText = data.choices[0].delta.content; + fullText += newText; + onText({ newText, fullText }); + } + } + } + + // Check one last time if the request has been aborted + if (aborted) { + return; + } + + if (!fullText) { + onError({ message: 'No response received from Mistral.', fullError: new Error('No response content') }); + return; + } + + onFinalMessage({ fullText }); + } catch (error: any) { + const errorMessage = error.message || JSON.stringify(error); + onError({ + message: `Mistral Error: ${errorMessage}`, + fullError: error + }); + } +}; diff --git a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts index b3970614..95716e81 100644 --- a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts @@ -11,6 +11,7 @@ import { sendOllamaMsg } from './ollama.js'; import { sendOpenAIMsg } from './openai.js'; import { sendGeminiMsg } from './gemini.js'; import { sendGroqMsg } from './groq.js'; +import { sendMistralMsg } from './mistral.js'; export const sendLLMMessage = ({ messages, @@ -97,6 +98,9 @@ export const sendLLMMessage = ({ case 'groq': sendGroqMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); break; + case 'mistral': + sendMistralMsg({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); + break; default: onError({ message: `Error: Void provider was "${providerName}", which is not recognized.`, fullError: null }) break; diff --git a/void b/void new file mode 160000 index 00000000..5083b8e9 --- /dev/null +++ b/void @@ -0,0 +1 @@ +Subproject commit 5083b8e971e48ae1001e5c8ceee7b010bcea3640