Merge pull request #5 from jcommaret/dev

Dev
This commit is contained in:
Jérôme Commaret 2025-04-08 19:48:22 +02:00 committed by GitHub
commit 2f078326c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 5880 additions and 3792 deletions

9428
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -41,3 +41,4 @@
"https://voideditor.dev"
]
}

View file

@ -23,7 +23,12 @@ const notifyYesUpdate = (notifService: INotificationService, res: { message?: st
severity: Severity.Info,
message: message,
sticky: true,
progress: { worked: 0, total: 100 },
// progress: { worked: 0, total: 100 },
// progress: { worked: 0, total: 100 },
neverShowAgain: {
id: 'voidUpdateNotification',
isSecondary: false
},
actions: {
primary: [{
id: 'void.updater.update',

View file

@ -56,13 +56,20 @@ export const defaultModelsOfProvider = {
'llama-3.1-8b-instant',
// 'qwen-2.5-coder-32b', // preview mode (experimental)
],
// not supporting mistral right now- it's last on Void usage, and a huge pain to set up since it's nonstandard (it supports codestral FIM but it's on v1/fim/completions, etc)
// mistral: [ // https://docs.mistral.ai/getting-started/models/models_overview/
// 'codestral-latest',
// 'mistral-large-latest',
// 'ministral-3b-latest',
// 'ministral-8b-latest',
// ],
mistral: [ // https://docs.mistral.ai/getting-started/models/models_overview/
'codestral-latest',
'mistral-large-latest',
'pixtral-large-latest',
'mistral-saba-latest',
'ministral-3b-latest',
'ministral-8b-latest',
'mistral-ocr-latest',
'mistral-embed',
'mistral-small-latest',
'pixtral-12b-2409',
'open-mistral-nemo',
'open-codestral-mamba'
],
openAICompatible: [], // fallback
} as const satisfies Record<ProviderName, string[]>
@ -127,11 +134,123 @@ const modelOptionsDefaults: ModelOptions = {
reasoningCapabilities: false,
}
const mistralModelOptions = {
'codestral-latest': {
contextWindow: 128_000,
maxOutputTokens: 16_384,
cost: { input: 0.000015, output: 0.000090 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'mistral-large-latest': {
contextWindow: 131_000,
maxOutputTokens: 8_192,
cost: { input: 0.000015, output: 0.000060 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'mistral-small-latest': {
contextWindow: 131_000,
maxOutputTokens: 8_192,
cost: { input: 0.000002, output: 0.000008 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'pixtral-large-latest': {
contextWindow: 128_000,
maxOutputTokens: 8_192,
cost: { input: 0.000025, output: 0.000075 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'mistral-saba-latest': {
contextWindow: 32_000,
maxOutputTokens: 8_192,
cost: { input: 0.000005, output: 0.000025 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'ministral-3b-latest': {
contextWindow: 131_000,
maxOutputTokens: 4_096,
cost: { input: 0.000001, output: 0.000003 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'ministral-8b-latest': {
contextWindow: 32_000,
maxOutputTokens: 4_096,
cost: { input: 0.000001, output: 0.000005 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'mistral-ocr-latest': {
contextWindow: 64_000,
maxOutputTokens: 4_096,
cost: { input: 0.000010, output: 0.000020 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'mistral-embed': {
contextWindow: 8_192,
maxOutputTokens: 0,
cost: { input: 0.000001, output: 0.0 },
supportsFIM: false,
supportsSystemMessage: false,
supportsTools: false,
reasoningCapabilities: false,
},
'pixtral-12b-2409': {
contextWindow: 128_000,
maxOutputTokens: 8_192,
cost: { input: 0.000020, output: 0.000060 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'open-mistral-nemo': {
contextWindow: 32_768,
maxOutputTokens: 4_096,
cost: { input: 0.0, output: 0.0 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: false,
reasoningCapabilities: false,
},
'open-codestral-mamba': {
contextWindow: 16_384,
maxOutputTokens: 4_096,
cost: { input: 0.0, output: 0.0 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: false,
reasoningCapabilities: false,
}
} as const satisfies { [s: string]: ModelOptions }
// TODO!!! double check all context sizes below
// TODO!!! add openrouter common models
// TODO!!! allow user to modify capabilities and tell them if autodetected model or falling back
const mistralSettings: ProviderSettings = {
...mistralModelOptions,
modelOptions: {},
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName),
}
const openSourceModelOptions_assumingOAICompat = {
'deepseekR1': {
@ -806,8 +925,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSetting
vLLM: vLLMSettings,
ollama: ollamaSettings,
openAICompatible: openaiCompatible,
// TODO!!!
mistral: mistralSettings,
// googleVertex: {},
// microsoftAzure: {},
// openHands: {},

View file

@ -43,6 +43,9 @@ export const defaultProviderSettings = {
xAI: {
apiKey: ''
},
mistral: {
apiKey: '',
},
} as const
@ -144,6 +147,11 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
title: 'Grok (xAI)',
}
}
else if (providerName === 'mistral') {
return {
title: 'Mistral AI',
}
}
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
@ -170,7 +178,8 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
providerName === 'groq' ? 'gsk_key...' :
providerName === 'openAICompatible' ? 'sk-key...' :
providerName === 'xAI' ? 'xai-key...' :
'',
providerName === 'mistral' ? 'api-key...' :
'',
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).' :
@ -179,8 +188,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
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 === 'xAI' ? 'Get your [API Key here](https://console.x.ai).' :
providerName === 'openAICompatible' ? undefined :
'',
providerName === 'mistral' ? 'Get your [API Key here](https://mistral.ai/settings/keys).' :
providerName === 'openAICompatible' ? undefined :
'',
isPasswordField: true,
}
}
@ -300,6 +310,12 @@ export const defaultSettingsOfProvider: SettingsOfProvider = {
...modelInfoOfDefaultModelNames(defaultModelsOfProvider.vLLM),
_didFillInProviderSettings: undefined,
},
mistral: { // aggregator
...defaultCustomSettings,
...defaultProviderSettings.mistral,
...modelInfoOfDefaultModelNames(defaultModelsOfProvider.mistral),
_didFillInProviderSettings: undefined,
},
}

View file

@ -7,6 +7,10 @@ import Anthropic from '@anthropic-ai/sdk';
import { Ollama } from 'ollama';
import OpenAI, { ClientOptions } from 'openai';
// Mistral FIM
import { MistralCore } from "@mistralai/mistralai/core.js";
import { fimComplete } from "@mistralai/mistralai/funcs/fimComplete.js";
//
import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper } from '../../common/helpers/extractCodeFromResult.js';
import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js';
import { defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
@ -113,6 +117,10 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay
const thisConfig = settingsOfProvider[providerName]
return new OpenAI({ baseURL: 'https://api.x.ai/v1', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
}
else if (providerName === 'mistral') {
const thisConfig = settingsOfProvider[providerName]
return new OpenAI({ baseURL: 'https://api.mistral.ai/v1', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
}
else throw new Error(`Void providerName was invalid: ${providerName}.`)
}
@ -420,6 +428,60 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
_setAborter(() => stream.controller.abort())
}
//////// MISTRAL ////////
const _sendMistralChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions }: SendChatParams_Internal) => {
_sendOpenAICompatibleChat({
messages: messages_,
onText,
onFinalMessage,
onError,
settingsOfProvider,
modelName: modelName_,
_setAborter,
providerName,
aiInstructions,
modelSelectionOptions
});
}
const _sendMistralFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions }: SendFIMParams_Internal) => {
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
if (!supportsFIM) {
if (modelName === modelName_)
onError({ message: `Model ${modelName} does not support FIM.`, fullError: null })
else
onError({ message: `Model ${modelName_} (${modelName}) does not support FIM.`, fullError: null })
return
}
prepareFIMMessage({ messages: messages_, aiInstructions })
const mistral = new MistralCore({ apiKey: settingsOfProvider.mistral.apiKey })
fimComplete(
mistral, {
model: modelName,
prompt: messages_.prefix,
suffix: messages_.suffix,
stream: false,
topP: 1,
stop: messages_.stopTokens
},
)
.then(async response => {
let content = response?.ok ? response.value.choices?.[0]?.message?.content : '';
const fullText = typeof content === 'string' ? content :
Array.isArray(content) ? content.map(chunk => chunk.type === 'text' ? chunk.text : '').join('') : '';
onFinalMessage({ fullText, fullReasoning: '', anthropicReasoning: null });
})
.catch(error => {
onError({ message: error + '', fullError: error });
})
}
// // in future, can do tool_use streaming in anthropic, but it's pretty fast even without streaming...
// const toolCallOfIndex: { [index: string]: { name: string, args: string } } = {}
// stream.on('streamEvent', e => {
@ -533,11 +595,11 @@ export const sendLLMMessageToProviderImplementation = {
sendFIM: null,
list: null,
},
// mistral: {
// sendChat: , // TODO
// sendFIM: , // TODO // https://docs.mistral.ai/api/#tag/fim
// list: null,
// },
mistral: {
sendChat: (params) => _sendMistralChat(params),
sendFIM: (params) => _sendMistralFIM(params),
list: null,
},
ollama: {
sendChat: (params) => _sendOpenAICompatibleChat(params),
sendFIM: sendOllamaFIM,