Merging everything here

This commit is contained in:
Jérôme Commaret 2025-01-13 22:48:43 +01:00
parent 52d89e5815
commit 278c52bd35
8 changed files with 254 additions and 7 deletions

1
.gitignore vendored
View file

@ -22,3 +22,4 @@ product.overrides.json
*.snap.actual
.vscode-test
.tmp/
.tool-versions

24
package-lock.json generated
View file

@ -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"
}
}
}
}

View file

@ -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",

62
src/package-lock.json generated Normal file
View file

@ -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"
}
}
}
}

View file

@ -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<CustomSettingName, undefined> = {
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,
}
}

View file

@ -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
});
}
};

View file

@ -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;

1
void Submodule

@ -0,0 +1 @@
Subproject commit 5083b8e971e48ae1001e5c8ceee7b010bcea3640