From 4065c5b504a8c5bc4a04aa57721e7f678055aa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Commaret?= Date: Fri, 27 Dec 2024 16:37:27 +0100 Subject: [PATCH] Mistral implementation : Working --- package-lock.json | 15 ++- package.json | 5 +- .../void/electron-main/llmMessage/mistral.ts | 94 ++++++++++++++++--- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 732ab358..91088265 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", @@ -64,10 +65,10 @@ "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": { - "@mistralai/mistralai": "^1.3.5", "@playwright/test": "^1.46.1", "@swc/core": "1.3.62", "@types/cookie": "^0.3.3", @@ -1954,7 +1955,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.3.5.tgz", "integrity": "sha512-yC91oJ5ScEPqbXmv3mJTwTFgu/ZtsYoOPOhaVXSsy6x4zXTqTI57yEC1flC9uiA8GpG/yhpn2BBUXF95+U9Blw==", - "dev": true, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19", @@ -23241,6 +23241,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 5039e401..23eee099 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,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", @@ -128,10 +129,10 @@ "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": { - "@mistralai/mistralai": "^1.3.5", "@playwright/test": "^1.46.1", "@swc/core": "1.3.62", "@types/cookie": "^0.3.3", diff --git a/src/vs/platform/void/electron-main/llmMessage/mistral.ts b/src/vs/platform/void/electron-main/llmMessage/mistral.ts index 3ba71d37..9a32e2b3 100644 --- a/src/vs/platform/void/electron-main/llmMessage/mistral.ts +++ b/src/vs/platform/void/electron-main/llmMessage/mistral.ts @@ -1,43 +1,109 @@ /*--------------------------------------------------------------------------------------------- * 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 MistralClient from '@mistralai/mistralai'; +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 = ''; const thisConfig = settingsOfProvider.mistral; - const mistral = new MistralClient({ + if (!thisConfig.apiKey) { + onError({ message: 'Mistral API key not configured.', fullError: new Error('No API key') }); + return; + } + + const mistral = new Mistral({ apiKey: thisConfig.apiKey }); 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: messages, + messages: mistralMessages, + temperature: 0.7, + maxTokens: 2048 }); - _setAborter(() => stream.controller.abort()); + _setAborter(() => { }); // Mistral does not provide an abort method for await (const chunk of stream) { - const newText = chunk.choices[0]?.delta?.content || ''; - if (newText) { - fullText += newText; - onText({ newText, fullText }); + 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 }); + } } } - onFinalMessage({ fullText }); - } catch (error) { - if (error.status === 401) { - onError({ message: 'Invalid API key.', fullError: error }); - } else { - onError({ message: error + '', fullError: error }); + 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 + }); } };