This commit is contained in:
Andrew Pareles 2025-01-25 18:39:37 -08:00
parent 018fd016c8
commit 3089bb31b5
7 changed files with 39 additions and 181 deletions

1
.gitignore vendored
View file

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

62
src/package-lock.json generated
View file

@ -1,62 +0,0 @@
{
"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

@ -117,9 +117,9 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
// read and update the actual state immediately
this._readState().then(readS => {
// THIS IS A HACK BECAUSE WE ADDED DEEPSEEK
const deepseekAdd = { deepseek: defaultSettingsOfProvider['deepseek'] }
readS = { ...readS, settingsOfProvider: { ...deepseekAdd, ...readS.settingsOfProvider, } }
// THIS IS A HACK BECAUSE WE ADDED DEEPSEEK AND MISTRAL
const additions = { deepseek: defaultSettingsOfProvider['deepseek'], mistral: defaultSettingsOfProvider['mistral'] }
readS = { ...readS, settingsOfProvider: { ...additions, ...readS.settingsOfProvider, } }
this.state = readS
resolver()

View file

@ -363,16 +363,16 @@ export const voidInitModelOptions = {
// used when waiting and for a type reference
export const defaultSettingsOfProvider: SettingsOfProvider = {
anthropic: {
_enabled: undefined,
...defaultCustomSettings,
...defaultProviderSettings.anthropic,
...voidInitModelOptions.anthropic,
_enabled: undefined,
},
openAI: {
_enabled: undefined,
...defaultCustomSettings,
...defaultProviderSettings.openAI,
...voidInitModelOptions.openAI,
_enabled: undefined,
},
deepseek: {
...defaultCustomSettings,

View file

@ -22,7 +22,7 @@ export const sendGroqMsg: _InternalSendLLMMessageFnType = async ({ messages, onT
messages: messages,
model: modelName,
stream: true,
temperature: 0.7,
// temperature: 0.7,
// max_tokens: parseMaxTokensStr(thisConfig.maxTokens),
})
.then(async response => {
@ -30,10 +30,8 @@ export const sendGroqMsg: _InternalSendLLMMessageFnType = async ({ messages, onT
// when receive text
for await (const chunk of response) {
const newText = chunk.choices[0]?.delta?.content || '';
if (newText) {
fullText += newText;
onText({ newText, fullText });
}
fullText += newText;
onText({ newText, fullText });
}
onFinalMessage({ fullText });

View file

@ -1,122 +1,46 @@
/*---------------------------------------------------------------------------------------------
* 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.
*--------------------------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
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
});
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
.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({
await mistral.chat
.stream({
messages: messages,
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;
stream: true,
// temperature: 0.7,
// maxTokens: 2048,
})
.then(async response => {
// Mistral has a really nonstandard API - no interrupt and weird stream types
_setAborter(() => { console.log('Mistral does not support interrupts! Further messages will just be ignored.') });
// when receive text
for await (const chunk of response) {
const c = chunk.data.choices[0].delta.content || ''
const newText = (
typeof c === 'string' ? c
: c?.map(c => c.type === 'text' ? c.text : c.type).join('\n')
)
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 });
}
}
}
// 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
});
}
};
onFinalMessage({ fullText });
})
.catch(error => {
onError({ message: error + '', fullError: error });
})
}

1
void

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