mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
add model overrides
This commit is contained in:
parent
d841468f3b
commit
d826052444
12 changed files with 388 additions and 182 deletions
|
|
@ -790,6 +790,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
console.log('starting autocomplete...', predictionType)
|
||||
|
||||
const featureName: FeatureName = 'Autocomplete'
|
||||
const overridesOfModel = this._settingsService.state.overridesOfModel
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
|
@ -807,6 +808,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
}),
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
logging: { loggingName: 'Autocomplete' },
|
||||
onText: () => { }, // unused in FIMMessage
|
||||
// onText: async ({ fullText, newText }) => {
|
||||
|
|
|
|||
|
|
@ -641,6 +641,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// above just defines helpers, below starts the actual function
|
||||
const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here
|
||||
const { overridesOfModel } = this._settingsService.state
|
||||
|
||||
let nMessagesSent = 0
|
||||
let shouldSendAnotherMessage = true
|
||||
|
|
@ -694,6 +695,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
messages: messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } },
|
||||
separateSystemMessage: separateSystemMessage,
|
||||
onText: ({ fullText, fullReasoning, toolCall }) => {
|
||||
|
|
|
|||
|
|
@ -607,20 +607,23 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
|
||||
prepareLLMSimpleMessages: IConvertToLLMMessageService['prepareLLMSimpleMessages'] = ({ simpleMessages, systemMessage, modelSelection, featureName }) => {
|
||||
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
|
||||
|
||||
const { overridesOfModel } = this.voidSettingsService.state
|
||||
|
||||
const { providerName, modelName } = modelSelection
|
||||
const {
|
||||
specialToolFormat,
|
||||
contextWindow,
|
||||
supportsSystemMessage,
|
||||
} = getModelCapabilities(providerName, modelName)
|
||||
} = getModelCapabilities(providerName, modelName, overridesOfModel)
|
||||
|
||||
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]
|
||||
|
||||
// Get combined AI instructions
|
||||
const aiInstructions = this._getCombinedAIInstructions();
|
||||
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
|
||||
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel)
|
||||
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled, overridesOfModel })
|
||||
|
||||
const { messages, separateSystemMessage } = prepareMessages({
|
||||
messages: simpleMessages,
|
||||
|
|
@ -637,12 +640,15 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
}
|
||||
prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection }) => {
|
||||
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
|
||||
|
||||
const { overridesOfModel } = this.voidSettingsService.state
|
||||
|
||||
const { providerName, modelName } = modelSelection
|
||||
const {
|
||||
specialToolFormat,
|
||||
contextWindow,
|
||||
supportsSystemMessage,
|
||||
} = getModelCapabilities(providerName, modelName)
|
||||
} = getModelCapabilities(providerName, modelName, overridesOfModel)
|
||||
const systemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat)
|
||||
|
||||
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName]
|
||||
|
|
@ -650,8 +656,8 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
// Get combined AI instructions
|
||||
const aiInstructions = this._getCombinedAIInstructions();
|
||||
|
||||
const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions)
|
||||
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
|
||||
const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions, overridesOfModel)
|
||||
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled, overridesOfModel })
|
||||
const llmMessages = this._chatMessagesToSimpleMessages(chatMessages)
|
||||
|
||||
const { messages, separateSystemMessage } = prepareMessages({
|
||||
|
|
|
|||
|
|
@ -106,37 +106,42 @@ const removeWhitespaceExceptNewlines = (str: string): string => {
|
|||
|
||||
// finds block.orig in fileContents and return its range in file
|
||||
// startingAtLine is 1-indexed and inclusive
|
||||
const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, opts: { startingAtLine?: number, returnType: 'lines' | 'indices' }) => {
|
||||
// returns 1-indexed lines
|
||||
const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, opts: { startingAtLine?: number, returnType: 'lines' }) => {
|
||||
|
||||
const startLineIdx = (fileContents: string) => opts?.startingAtLine !== undefined ?
|
||||
const returnAns = (fileContents: string, idx: number) => {
|
||||
const startLine = numLinesOfStr(fileContents.substring(0, idx + 1))
|
||||
const numLines = numLinesOfStr(text)
|
||||
const endLine = startLine + numLines - 1
|
||||
|
||||
return [startLine, endLine] as const
|
||||
}
|
||||
|
||||
const startingAtLineIdx = (fileContents: string) => opts?.startingAtLine !== undefined ?
|
||||
fileContents.split('\n').slice(0, opts.startingAtLine).join('\n').length // num characters in all lines before startingAtLine
|
||||
: 0
|
||||
|
||||
// idx = starting index in fileContents
|
||||
let idx = fileContents.indexOf(text, startLineIdx(fileContents))
|
||||
let idx = fileContents.indexOf(text, startingAtLineIdx(fileContents))
|
||||
|
||||
// if idx was found
|
||||
if (idx !== -1) {
|
||||
return returnAns(fileContents, idx)
|
||||
}
|
||||
|
||||
if (!canFallbackToRemoveWhitespace)
|
||||
return 'Not found' as const
|
||||
|
||||
// try to find it ignoring all whitespace this time
|
||||
if (idx === -1 && canFallbackToRemoveWhitespace) {
|
||||
text = removeWhitespaceExceptNewlines(text)
|
||||
fileContents = removeWhitespaceExceptNewlines(fileContents)
|
||||
idx = fileContents.indexOf(text, startLineIdx(fileContents));
|
||||
}
|
||||
text = removeWhitespaceExceptNewlines(text)
|
||||
fileContents = removeWhitespaceExceptNewlines(fileContents)
|
||||
idx = fileContents.indexOf(text, startingAtLineIdx(fileContents));
|
||||
|
||||
if (idx === -1) return 'Not found' as const
|
||||
const lastIdx = fileContents.lastIndexOf(text)
|
||||
if (lastIdx !== idx) return 'Not unique' as const
|
||||
|
||||
if (opts.returnType === 'lines') {
|
||||
const startLine = fileContents.substring(0, idx).split('\n').length
|
||||
const numLines = numLinesOfStr(text)
|
||||
const endLine = startLine + numLines - 1
|
||||
return [startLine, endLine] as const
|
||||
}
|
||||
|
||||
else if (opts.returnType === 'indices') {
|
||||
return [idx, idx + text.length] as const
|
||||
}
|
||||
else throw new Error(`findTextInCode: Invalid returnType ${opts.returnType}`)
|
||||
return returnAns(fileContents, idx)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1331,6 +1336,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
const { from, } = opts
|
||||
const featureName: FeatureName = opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K'
|
||||
const overridesOfModel = this._settingsService.state.overridesOfModel
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
|
@ -1482,6 +1488,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
separateSystemMessage,
|
||||
chatMode: null, // not chat
|
||||
onText: (params) => {
|
||||
|
|
@ -1556,17 +1563,30 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
private _errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => {
|
||||
|
||||
/**
|
||||
* Generates a human-readable error message for an invalid ORIGINAL search block.
|
||||
*/
|
||||
private _errContentOfInvalidStr = (
|
||||
str: 'Not found' | 'Not unique' | 'Has overlap',
|
||||
blockOrig: string,
|
||||
): string => {
|
||||
const problematicCode = `${tripleTick[0]}\n${JSON.stringify(blockOrig)}\n${tripleTick[1]}`
|
||||
|
||||
const descStr = str === `Not found` ?
|
||||
`The edit was not applied. The text in ORIGINAL must EXACTLY match lines of code in the file, but there was no match for:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code matches a code excerpt exactly.`
|
||||
: str === `Not unique` ?
|
||||
`The edit was not applied. The text in ORIGINAL must be unique in the file being edited, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.`
|
||||
: str === 'Has overlap' ?
|
||||
`The edit was not applied. The text in the ORIGINAL blocks must not overlap, but the following ORIGINAL code had overlap with another ORIGINAL string:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code blocks do not overlap.`
|
||||
: ``
|
||||
// use a switch for better readability / exhaustiveness check
|
||||
let descStr: string
|
||||
switch (str) {
|
||||
case 'Not found':
|
||||
descStr = `The edit was not applied. The text in ORIGINAL must EXACTLY match lines of code in the file, but there was no match for:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code matches a code excerpt exactly.`
|
||||
break
|
||||
case 'Not unique':
|
||||
descStr = `The edit was not applied. The text in ORIGINAL must be unique in the file being edited, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.`
|
||||
break
|
||||
case 'Has overlap':
|
||||
descStr = `The edit was not applied. The text in the ORIGINAL blocks must not overlap, but the following ORIGINAL code had overlap with another ORIGINAL string:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code blocks do not overlap.`
|
||||
break
|
||||
default:
|
||||
descStr = ''
|
||||
}
|
||||
return descStr
|
||||
}
|
||||
|
||||
|
|
@ -1578,22 +1598,34 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
const { model } = this._voidModelService.getModel(uri)
|
||||
if (!model) throw new Error(`Error applying Search/Replace blocks: File does not exist.`)
|
||||
const modelStr = model.getValue(EndOfLinePreference.LF)
|
||||
// .split('\n').map(l => '\t' + l).join('\n') // for testing purposes only, remember to remove this
|
||||
const modelStrLines = modelStr.split('\n')
|
||||
|
||||
|
||||
|
||||
|
||||
const replacements: { origStart: number; origEnd: number; block: ExtractedSearchReplaceBlock }[] = []
|
||||
for (const b of blocks) {
|
||||
const res = findTextInCode(b.orig, modelStr, true, { returnType: 'indices' })
|
||||
const res = findTextInCode(b.orig, modelStr, true, { returnType: 'lines' })
|
||||
if (typeof res === 'string')
|
||||
throw new Error(this._errContentOfInvalidStr(res, b.orig))
|
||||
const [i, _] = res
|
||||
let [startLine, endLine] = res
|
||||
startLine -= 1 // 0-index
|
||||
endLine -= 1
|
||||
|
||||
replacements.push({
|
||||
origStart: i,
|
||||
origEnd: i + b.orig.length - 1, // INCLUSIVE
|
||||
block: b,
|
||||
})
|
||||
// including newline before start
|
||||
const contentBeforeStart = startLine !== 0 ?
|
||||
modelStrLines.slice(0, startLine).join('\n') + '\n'
|
||||
: ''
|
||||
|
||||
// including endline at end
|
||||
const contentUpToEnd = modelStrLines.slice(0, endLine + 1).join('\n')
|
||||
|
||||
const origStart = contentBeforeStart.length;
|
||||
const origEnd = contentUpToEnd.length;
|
||||
|
||||
replacements.push({ origStart, origEnd, block: b });
|
||||
}
|
||||
|
||||
// sort in increasing order
|
||||
replacements.sort((a, b) => a.origStart - b.origStart)
|
||||
|
||||
|
|
@ -1610,17 +1642,18 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
const { origStart, origEnd, block } = replacements[i]
|
||||
newCode = newCode.slice(0, origStart) + block.final + newCode.slice(origEnd + 1, Infinity)
|
||||
}
|
||||
console.log('REPLACEMENTS', replacements, newCode)
|
||||
|
||||
this._writeURIText(uri, newCode,
|
||||
'wholeFileRange',
|
||||
{ shouldRealignDiffAreas: true }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise<void>] | undefined {
|
||||
const { from, applyStr, } = opts
|
||||
const featureName: FeatureName = 'Apply'
|
||||
const overridesOfModel = this._settingsService.state.overridesOfModel
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
|
@ -1900,6 +1933,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
separateSystemMessage,
|
||||
chatMode: null, // not chat
|
||||
onText: (params) => {
|
||||
|
|
|
|||
|
|
@ -153,14 +153,16 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) =>
|
|||
const voidSettingsState = useSettingsState()
|
||||
|
||||
const modelSelection = voidSettingsState.modelSelectionOfFeature[featureName]
|
||||
const overridesOfModel = voidSettingsState.overridesOfModel
|
||||
|
||||
if (!modelSelection) return null
|
||||
|
||||
const { modelName, providerName } = modelSelection
|
||||
const { reasoningCapabilities } = getModelCapabilities(providerName, modelName)
|
||||
const { reasoningCapabilities } = getModelCapabilities(providerName, modelName, overridesOfModel)
|
||||
const { canTurnOffReasoning, reasoningBudgetSlider } = reasoningCapabilities || {}
|
||||
|
||||
const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[modelName]
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel)
|
||||
if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now)
|
||||
return null // unused right now
|
||||
// return <div className='flex items-center gap-x-2'>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const MemoizedModelDropdown = ({ featureName, className }: { featureName: Featur
|
|||
|
||||
useEffect(() => {
|
||||
const oldOptions = oldOptionsRef.current
|
||||
const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode }))
|
||||
const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode, overridesOfModel: settingsState.overridesOfModel }))
|
||||
|
||||
if (!optionsEqual(oldOptions, newOptions)) {
|
||||
setMemoizedOptions(newOptions)
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam
|
|||
|
||||
|
||||
// Import the getModelCapabilities function to access default values
|
||||
import { defaultModelOptions, getModelCapabilities, ModelOverrideOptions } from '../../../../common/modelCapabilities.js';
|
||||
import { getModelCapabilities, ModelOverrideOptions } from '../../../../common/modelCapabilities.js';
|
||||
|
||||
// Modal dialog to show model settings
|
||||
const ModelSettingsDialog = ({
|
||||
|
|
@ -324,45 +324,68 @@ const ModelSettingsDialog = ({
|
|||
|
||||
// Get current model capabilities and override settings
|
||||
const modelCapabilities = getModelCapabilities(providerName, modelName, settingsState.overridesOfModel);
|
||||
const defaultModelCapabilities = getModelCapabilities(providerName, modelName, undefined)
|
||||
|
||||
// Initialize form state for all potential override options
|
||||
const [formValues, setFormValues] = useState<{
|
||||
contextWindow: string;
|
||||
maxOutputTokens: string;
|
||||
supportsTools: 'openai-style' | undefined | '';
|
||||
supportsSystemMessage: 'system-role' | 'developer-role' | false | '';
|
||||
specialToolFormat: 'openai-style' | 'gemini-style' | 'anthropic-style' | undefined | '';
|
||||
supportsSystemMessage: 'system-role' | 'developer-role' | 'separated' | false | '';
|
||||
supportsFIM: boolean | null;
|
||||
reasoningCapabilities: boolean | null;
|
||||
canTurnOffReasoning: boolean;
|
||||
reasoningMaxOutputTokens: string;
|
||||
openSourceThinkTags: [string, string] | null;
|
||||
}>({
|
||||
// start form as default values
|
||||
contextWindow: '',
|
||||
maxOutputTokens: '',
|
||||
supportsTools: '',
|
||||
specialToolFormat: '',
|
||||
supportsSystemMessage: '',
|
||||
supportsFIM: null,
|
||||
reasoningCapabilities: null
|
||||
reasoningCapabilities: null,
|
||||
canTurnOffReasoning: false,
|
||||
reasoningMaxOutputTokens: '',
|
||||
openSourceThinkTags: null,
|
||||
});
|
||||
|
||||
// When dialog opens or model changes, reset form values
|
||||
useEffect(() => {
|
||||
if (isOpen && modelInfo) {
|
||||
|
||||
// Get current overrides
|
||||
const overrides = settingsState.overridesOfModel?.[providerName]?.[modelName] || {};
|
||||
|
||||
// Extract reasoning capabilities if available (use any to avoid TS union narrowing issues)
|
||||
const reasoningCapabilities: any = typeof overrides.reasoningCapabilities === 'object' ?
|
||||
overrides.reasoningCapabilities : overrides.reasoningCapabilities ? { supportsReasoning: true, canIOReasoning: true } : false;
|
||||
|
||||
// Extract the think tags if they exist
|
||||
let thinkTags: [string, string] | null = null;
|
||||
if (typeof reasoningCapabilities === 'object' && reasoningCapabilities.openSourceThinkTags) {
|
||||
thinkTags = reasoningCapabilities.openSourceThinkTags as [string, string];
|
||||
}
|
||||
|
||||
// Only set values that are explicitly overridden, otherwise leave them empty
|
||||
// to indicate default values should be used
|
||||
setFormValues({
|
||||
contextWindow: (overrides.contextWindow !== undefined) ? overrides.contextWindow?.toString() : '',
|
||||
maxOutputTokens: (overrides.maxOutputTokens !== undefined) ? overrides.maxOutputTokens?.toString() : '',
|
||||
supportsTools: overrides.supportsTools !== undefined ? overrides.supportsTools : '',
|
||||
contextWindow: overrides.contextWindow !== undefined ? String(overrides.contextWindow) : '',
|
||||
maxOutputTokens: overrides.maxOutputTokens !== undefined ? String(overrides.maxOutputTokens) : '',
|
||||
specialToolFormat: overrides.specialToolFormat !== undefined ? overrides.specialToolFormat : '',
|
||||
supportsSystemMessage: overrides.supportsSystemMessage !== undefined ? overrides.supportsSystemMessage : '',
|
||||
supportsFIM: overrides.supportsFIM !== undefined ? overrides.supportsFIM : null,
|
||||
reasoningCapabilities: overrides.reasoningCapabilities !== undefined ?
|
||||
!!overrides.reasoningCapabilities : null
|
||||
!!overrides.reasoningCapabilities : null,
|
||||
canTurnOffReasoning: typeof reasoningCapabilities === 'object' ? !!reasoningCapabilities.canTurnOffReasoning : false,
|
||||
reasoningMaxOutputTokens: typeof reasoningCapabilities === 'object' && reasoningCapabilities.reasoningMaxOutputTokens ?
|
||||
String(reasoningCapabilities.reasoningMaxOutputTokens) : '',
|
||||
openSourceThinkTags: thinkTags,
|
||||
});
|
||||
}
|
||||
}, [isOpen, modelInfo, settingsState.overridesOfModel, providerName, modelName]);
|
||||
|
||||
// Update a single field in the form
|
||||
const updateField = (field: string, value: any) => {
|
||||
const updateField = (field: keyof typeof formValues, value: any) => {
|
||||
setFormValues(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
|
|
@ -371,52 +394,78 @@ const ModelSettingsDialog = ({
|
|||
|
||||
// Handle saving settings
|
||||
const handleSave = async () => {
|
||||
const settings: ModelOverrideOptions = {};
|
||||
// Get current overrides to see what needs to be updated/removed
|
||||
const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] || {};
|
||||
const newSettings: ModelOverrideOptions = {};
|
||||
|
||||
// Only add fields to the override if they have been changed from defaults
|
||||
if (formValues.contextWindow) {
|
||||
// Handle numeric fields - empty strings should remove the override
|
||||
if (formValues.contextWindow.trim() === '') {
|
||||
newSettings.contextWindow = defaultModelCapabilities.contextWindow;
|
||||
} else if (formValues.contextWindow) {
|
||||
const tokens = parseInt(formValues.contextWindow);
|
||||
if (!isNaN(tokens)) settings.contextWindow = tokens;
|
||||
if (!isNaN(tokens)) newSettings.contextWindow = tokens;
|
||||
}
|
||||
|
||||
if (formValues.maxOutputTokens) {
|
||||
if (formValues.maxOutputTokens.trim() === '') {
|
||||
newSettings.maxOutputTokens = defaultModelCapabilities.maxOutputTokens;
|
||||
} else if (formValues.maxOutputTokens) {
|
||||
const tokens = parseInt(formValues.maxOutputTokens);
|
||||
if (!isNaN(tokens)) settings.maxOutputTokens = tokens;
|
||||
if (!isNaN(tokens)) newSettings.maxOutputTokens = tokens;
|
||||
}
|
||||
|
||||
if (formValues.supportsTools !== '') {
|
||||
settings.supportsTools = formValues.supportsTools as any;
|
||||
// Handle dropdown fields
|
||||
if (formValues.specialToolFormat === '') {
|
||||
newSettings.specialToolFormat = defaultModelCapabilities.specialToolFormat
|
||||
} else {
|
||||
newSettings.specialToolFormat = formValues.specialToolFormat
|
||||
}
|
||||
|
||||
if (formValues.supportsSystemMessage !== '') {
|
||||
settings.supportsSystemMessage = formValues.supportsSystemMessage as any;
|
||||
if (formValues.supportsSystemMessage === '') {
|
||||
newSettings.supportsSystemMessage = defaultModelCapabilities.supportsSystemMessage;
|
||||
} else {
|
||||
newSettings.supportsSystemMessage = formValues.supportsSystemMessage as any;
|
||||
}
|
||||
|
||||
if (formValues.supportsFIM !== null) {
|
||||
settings.supportsFIM = formValues.supportsFIM;
|
||||
if (formValues.supportsFIM === null) {
|
||||
newSettings.supportsFIM = defaultModelCapabilities.supportsFIM
|
||||
} else {
|
||||
newSettings.supportsFIM = formValues.supportsFIM;
|
||||
}
|
||||
|
||||
if (formValues.reasoningCapabilities !== null) {
|
||||
if (formValues.reasoningCapabilities) {
|
||||
settings.reasoningCapabilities = {
|
||||
supportsReasoning: true,
|
||||
canTurnOffReasoning: true,
|
||||
canIOReasoning: true
|
||||
};
|
||||
} else {
|
||||
settings.reasoningCapabilities = false;
|
||||
if (formValues.reasoningCapabilities === null) {
|
||||
newSettings.reasoningCapabilities = defaultModelCapabilities.reasoningCapabilities;
|
||||
} else if (formValues.reasoningCapabilities) {
|
||||
const reasoningSettings: any = {
|
||||
supportsReasoning: true,
|
||||
canIOReasoning: true,
|
||||
canTurnOffReasoning: formValues.canTurnOffReasoning
|
||||
};
|
||||
|
||||
// Only add these if they have values
|
||||
if (formValues.reasoningMaxOutputTokens) {
|
||||
reasoningSettings.reasoningMaxOutputTokens = parseInt(formValues.reasoningMaxOutputTokens);
|
||||
}
|
||||
|
||||
if (formValues.openSourceThinkTags) {
|
||||
reasoningSettings.openSourceThinkTags = formValues.openSourceThinkTags;
|
||||
}
|
||||
|
||||
newSettings.reasoningCapabilities = reasoningSettings;
|
||||
} else {
|
||||
newSettings.reasoningCapabilities = false;
|
||||
}
|
||||
|
||||
await settingsStateService.setOverridesOfModel(providerName, modelName, settings);
|
||||
await settingsStateService.setOverridesOfModel(providerName, modelName, newSettings);
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={onClose}>
|
||||
<div className="bg-void-bg-1 rounded-md p-4 max-w-md w-full shadow-xl overflow-y-auto max-h-[90vh]" onClick={e => e.stopPropagation()}>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-medium">Override defaults for {modelName} ({displayInfoOfProviderName(providerName).title})</h3>
|
||||
<h3 className="text-lg font-medium">Change Defaults for {modelName} ({displayInfoOfProviderName(providerName).title})</h3>
|
||||
<button onClick={onClose} className="text-void-fg-3 hover:text-void-fg-1">
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
|
|
@ -429,25 +478,51 @@ const ModelSettingsDialog = ({
|
|||
{/* Context window */}
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-void-fg-2">Context window (tokens)</span>
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.contextWindow}
|
||||
onChangeValue={(value) => updateField('contextWindow', value)}
|
||||
placeholder={(modelCapabilities.contextWindow || defaultModelOptions.contextWindow) + ''}
|
||||
compact={true}
|
||||
className="max-w-24"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoidSwitch
|
||||
size="xxs"
|
||||
value={formValues.contextWindow !== ''}
|
||||
onChange={(enabled) => {
|
||||
updateField('contextWindow', enabled ? String(defaultModelCapabilities.contextWindow) : '');
|
||||
}}
|
||||
/>
|
||||
{formValues.contextWindow === '' ? (
|
||||
<span className="text-void-fg-3 text-xs w-24 text-right">Default ({defaultModelCapabilities.contextWindow})</span>
|
||||
) : (
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.contextWindow}
|
||||
onChangeValue={(value) => updateField('contextWindow', value)}
|
||||
placeholder={String(defaultModelCapabilities.contextWindow)}
|
||||
compact={true}
|
||||
className="max-w-24"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Maximum output tokens */}
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-void-fg-2">Maximum output tokens</span>
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.maxOutputTokens}
|
||||
onChangeValue={(value) => updateField('maxOutputTokens', value)}
|
||||
placeholder={(modelCapabilities.maxOutputTokens || defaultModelOptions.maxOutputTokens) + ''}
|
||||
compact={true}
|
||||
className="max-w-24"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoidSwitch
|
||||
size="xxs"
|
||||
value={formValues.maxOutputTokens !== ''}
|
||||
onChange={(enabled) => {
|
||||
updateField('maxOutputTokens', enabled ? String(defaultModelCapabilities.maxOutputTokens) : '');
|
||||
}}
|
||||
/>
|
||||
{formValues.maxOutputTokens === '' ? (
|
||||
<span className="text-void-fg-3 text-xs w-24 text-right">Default ({defaultModelCapabilities.maxOutputTokens})</span>
|
||||
) : (
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.maxOutputTokens}
|
||||
onChangeValue={(value) => updateField('maxOutputTokens', value)}
|
||||
placeholder={String(defaultModelCapabilities.maxOutputTokens)}
|
||||
compact={true}
|
||||
className="max-w-24"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Supports Tools */}
|
||||
|
|
@ -455,10 +530,10 @@ const ModelSettingsDialog = ({
|
|||
<span className="text-void-fg-2">Supports tools</span>
|
||||
<VoidCustomDropdownBox
|
||||
options={['', 'openai-style']}
|
||||
selectedOption={formValues.supportsTools}
|
||||
onChangeOption={(value) => updateField('supportsTools', value)}
|
||||
selectedOption={formValues.specialToolFormat}
|
||||
onChangeOption={(value) => updateField('specialToolFormat', value)}
|
||||
getOptionDisplayName={(opt) => {
|
||||
if (opt === '') return `Default (${modelCapabilities.specialToolFormat || 'No'})`;
|
||||
if (opt === '') return `Default (${defaultModelCapabilities.specialToolFormat || 'No'})`;
|
||||
return opt;
|
||||
}}
|
||||
getOptionDropdownName={(opt) => {
|
||||
|
|
@ -478,7 +553,7 @@ const ModelSettingsDialog = ({
|
|||
selectedOption={formValues.supportsSystemMessage}
|
||||
onChangeOption={(value) => updateField('supportsSystemMessage', value)}
|
||||
getOptionDisplayName={(opt) => {
|
||||
if (opt === '') return `Default (${modelCapabilities.supportsSystemMessage || 'No'})`;
|
||||
if (opt === '') return `Default (${defaultModelCapabilities.supportsSystemMessage || 'No'})`;
|
||||
if (opt === false) return 'No'
|
||||
if (opt === true) return 'Yes' // should never happen
|
||||
return opt;
|
||||
|
|
@ -502,7 +577,7 @@ const ModelSettingsDialog = ({
|
|||
selectedOption={formValues.supportsFIM}
|
||||
onChangeOption={(value) => updateField('supportsFIM', value)}
|
||||
getOptionDisplayName={(opt) => {
|
||||
if (opt === null) return `Default (${modelCapabilities.supportsFIM ? 'Yes' : 'No'})`;
|
||||
if (opt === null) return `Default (${defaultModelCapabilities.supportsFIM ? 'Yes' : 'No'})`;
|
||||
return opt ? 'Yes' : 'No';
|
||||
}}
|
||||
getOptionDropdownName={(opt) => {
|
||||
|
|
@ -522,7 +597,7 @@ const ModelSettingsDialog = ({
|
|||
selectedOption={formValues.reasoningCapabilities}
|
||||
onChangeOption={(value) => updateField('reasoningCapabilities', value)}
|
||||
getOptionDisplayName={(opt) => {
|
||||
if (opt === null) return `Default (${modelCapabilities.reasoningCapabilities ? 'Yes' : 'No'})`;
|
||||
if (opt === null) return `Default (${defaultModelCapabilities.reasoningCapabilities ? 'Yes' : 'No'})`;
|
||||
return opt ? 'Yes' : 'No';
|
||||
}}
|
||||
getOptionDropdownName={(opt) => {
|
||||
|
|
@ -533,6 +608,100 @@ const ModelSettingsDialog = ({
|
|||
className="max-w-32 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Additional reasoning options - only show when reasoning is enabled */}
|
||||
{formValues.reasoningCapabilities && (
|
||||
<>
|
||||
{/* Can Turn Off Reasoning */}
|
||||
<div className="flex items-center justify-between py-1 pl-6">
|
||||
<span className="text-void-fg-2">Allow turning off reasoning</span>
|
||||
<VoidCustomDropdownBox
|
||||
options={[true, false]}
|
||||
selectedOption={formValues.canTurnOffReasoning}
|
||||
onChangeOption={(value) => updateField('canTurnOffReasoning', value)}
|
||||
getOptionDisplayName={(opt) => opt ? 'Yes' : 'No'}
|
||||
getOptionDropdownName={(opt) => opt ? 'Yes' : 'No'}
|
||||
getOptionsEqual={(a, b) => a === b}
|
||||
className="max-w-32 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Reasoning Max Output Tokens - only shown if canTurnOffReasoning is true */}
|
||||
{formValues.canTurnOffReasoning && (
|
||||
<div className="flex items-center justify-between py-1 pl-6">
|
||||
<span className="text-void-fg-2">Max output tokens when reasoning</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoidSwitch
|
||||
size="xxs"
|
||||
value={formValues.reasoningMaxOutputTokens !== ''}
|
||||
onChange={(enabled) => {
|
||||
// Use a reasonable default value when enabling
|
||||
const defaultValue = defaultModelCapabilities.maxOutputTokens || 500;
|
||||
updateField('reasoningMaxOutputTokens', enabled ? String(defaultValue) : '');
|
||||
}}
|
||||
/>
|
||||
{formValues.reasoningMaxOutputTokens === '' ? (
|
||||
<span className="text-void-fg-3 text-xs w-24 text-right">Default</span>
|
||||
) : (
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.reasoningMaxOutputTokens}
|
||||
onChangeValue={(value) => updateField('reasoningMaxOutputTokens', value)}
|
||||
placeholder="Default"
|
||||
compact={true}
|
||||
className="max-w-24"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Open Source Think Tags Toggle + Input Fields */}
|
||||
<div className="flex items-center justify-between py-1 pl-6">
|
||||
<span className="text-void-fg-2">Open source think tags</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoidSwitch
|
||||
size="xxs"
|
||||
value={formValues.openSourceThinkTags !== null}
|
||||
onChange={(enabled) => {
|
||||
if (enabled) {
|
||||
// Enable with default values
|
||||
updateField('openSourceThinkTags', ['<think>', '</think>']);
|
||||
} else {
|
||||
// Disable
|
||||
updateField('openSourceThinkTags', null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{formValues.openSourceThinkTags !== null && (
|
||||
<div className="flex gap-1 items-center">
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.openSourceThinkTags ? formValues.openSourceThinkTags[0] : ''}
|
||||
onChangeValue={(value) => {
|
||||
const currentTags = formValues.openSourceThinkTags || ['', ''];
|
||||
updateField('openSourceThinkTags', [value, currentTags[1]]);
|
||||
}}
|
||||
placeholder="<think>"
|
||||
compact={true}
|
||||
className="max-w-16"
|
||||
/>
|
||||
<span className="text-void-fg-3">...</span>
|
||||
<VoidSimpleInputBox
|
||||
value={formValues.openSourceThinkTags ? formValues.openSourceThinkTags[1] : ''}
|
||||
onChangeValue={(value) => {
|
||||
const currentTags = formValues.openSourceThinkTags || ['', ''];
|
||||
updateField('openSourceThinkTags', [currentTags[0], value]);
|
||||
}}
|
||||
placeholder="</think>"
|
||||
compact={true}
|
||||
className="max-w-16"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -587,8 +756,8 @@ export const ModelDump = () => {
|
|||
|
||||
const tooltipName = (
|
||||
disabled ? `Add ${providerTitle} to enable`
|
||||
: value === true ? 'Enabled'
|
||||
: 'Disabled'
|
||||
: value === true ? 'Show in Dropdown'
|
||||
: 'Hide from Dropdown'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -601,50 +770,39 @@ export const ModelDump = () => {
|
|||
|
||||
|
||||
return <div key={`${modelName}${providerName}`}
|
||||
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate
|
||||
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate group
|
||||
`}
|
||||
>
|
||||
{/* left part is width:full */}
|
||||
<div className={`flex-grow flex items-center gap-4`}>
|
||||
<div className={`flex flex-grow items-center gap-4`}>
|
||||
<span className='w-full max-w-32'>{isNewProviderName ? providerTitle : ''}</span>
|
||||
<span className='w-fit truncate'>{modelName}{detailAboutModel}</span>
|
||||
<span className='w-fit truncate'>{modelName}</span>
|
||||
</div>
|
||||
|
||||
{/* right part is anything that fits */}
|
||||
<div className='flex items-center gap-4'
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-place='top'
|
||||
// data-tooltip-content={disabled ? `${displayInfoOfProviderName(providerName).title} is disabled`
|
||||
// : (isHidden ? `'${modelName}' won't appear in dropdowns` : ``)
|
||||
// }
|
||||
>
|
||||
|
||||
|
||||
{/* <span className='opacity-50 truncate'>{type === 'autodetected' ? '(detected locally)' : type === 'default' ? '' : '(custom model)'}</span> */}
|
||||
|
||||
{/* Settings button - only for custom or locally detected models */}
|
||||
{(type === 'autodetected' || type === 'custom') && (
|
||||
<div className="w-5 flex items-center justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpenSettingsModel({
|
||||
modelName,
|
||||
providerName,
|
||||
type
|
||||
})
|
||||
}}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='right'
|
||||
data-tooltip-content="Model Settings"
|
||||
>
|
||||
<SettingsIcon size={14} className="text-void-fg-3" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-4 w-fit">
|
||||
|
||||
{/* Advanced Settings button - only for custom or locally detected models */}
|
||||
<div className="w-5 flex items-center justify-center">
|
||||
<button
|
||||
onClick={() => { setOpenSettingsModel({ modelName, providerName, type }) }}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='right'
|
||||
data-tooltip-content='Advanced Settings'
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<SettingsIcon size={14} className="text-void-fg-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Blue star */}
|
||||
{detailAboutModel}
|
||||
|
||||
|
||||
{/* Switch */}
|
||||
<VoidSwitch
|
||||
value={value}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName); }}
|
||||
disabled={disabled}
|
||||
size='sm'
|
||||
|
||||
|
|
@ -653,8 +811,9 @@ export const ModelDump = () => {
|
|||
data-tooltip-content={tooltipName}
|
||||
/>
|
||||
|
||||
{/* X button */}
|
||||
<div className={`w-5 flex items-center justify-center`}>
|
||||
{type === 'default' || type === 'autodetected' ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName) }}><X className='size-4' /></button>}
|
||||
{type === 'default' || type === 'autodetected' ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName); }}><X className="size-4" /></button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1301,7 +1460,7 @@ export const Settings = () => {
|
|||
<span
|
||||
className='hover:brightness-110'
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
|
||||
data-tooltip-content='We recommend using the largest qwen2.5-coder model you can with Ollama (try qwen2.5-coder:3b).'
|
||||
data-tooltip-class-name='void-max-w-[20px]'
|
||||
>
|
||||
Only works with FIM models.*
|
||||
|
|
|
|||
|
|
@ -173,22 +173,9 @@ export type VoidStaticModelInfo = { // not stateful
|
|||
}
|
||||
|
||||
|
||||
export type ModelOverrideOptions = Partial<{
|
||||
contextWindow: number; // input tokens
|
||||
maxOutputTokens: number; // output tokens, defaults to 4092
|
||||
supportsTools: 'openai-style' | undefined;
|
||||
supportsSystemMessage: 'system-role' | 'developer-role' | false;
|
||||
supportsFIM: boolean;
|
||||
reasoningCapabilities: false | {
|
||||
readonly supportsReasoning: true;
|
||||
readonly canTurnOffReasoning: boolean;
|
||||
readonly canIOReasoning: boolean;
|
||||
readonly reasoningMaxOutputTokens?: number;
|
||||
readonly openSourceThinkTags?: [string, string];
|
||||
}
|
||||
}>
|
||||
|
||||
|
||||
export type ModelOverrideOptions = Partial<Pick<VoidStaticModelInfo,
|
||||
'contextWindow' | 'maxOutputTokens' | 'specialToolFormat' | 'supportsSystemMessage' | 'supportsFIM' | 'reasoningCapabilities'
|
||||
>>
|
||||
|
||||
|
||||
|
||||
|
|
@ -210,7 +197,7 @@ type VoidStaticProviderInfo = { // doesn't change (not stateful)
|
|||
|
||||
|
||||
|
||||
export const defaultModelOptions = {
|
||||
const defaultModelOptions = {
|
||||
contextWindow: 4_096,
|
||||
maxOutputTokens: 4_096,
|
||||
cost: { input: 0, output: 0 },
|
||||
|
|
@ -1155,7 +1142,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi
|
|||
export const getModelCapabilities = (
|
||||
providerName: ProviderName,
|
||||
modelName: string,
|
||||
overridesOfModel?: OverridesOfModel
|
||||
overridesOfModel: OverridesOfModel | undefined
|
||||
): VoidStaticModelInfo & { modelName: string; isUnrecognizedModel: boolean } => {
|
||||
|
||||
const lowercaseModelName = modelName.toLowerCase()
|
||||
|
|
@ -1201,8 +1188,9 @@ export const getIsReasoningEnabledState = (
|
|||
providerName: ProviderName,
|
||||
modelName: string,
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined,
|
||||
overridesOfModel: OverridesOfModel | undefined,
|
||||
) => {
|
||||
const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {}
|
||||
const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {}
|
||||
if (!supportsReasoning) return false
|
||||
|
||||
// default to enabled if can't turn off, or if the featureName is Chat.
|
||||
|
|
@ -1213,11 +1201,11 @@ export const getIsReasoningEnabledState = (
|
|||
}
|
||||
|
||||
|
||||
export const getMaxOutputTokens = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean }) => {
|
||||
export const getMaxOutputTokens = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean, overridesOfModel: OverridesOfModel | undefined }) => {
|
||||
const {
|
||||
reasoningCapabilities,
|
||||
maxOutputTokens
|
||||
} = getModelCapabilities(providerName, modelName)
|
||||
maxOutputTokens,
|
||||
} = getModelCapabilities(providerName, modelName, opts.overridesOfModel)
|
||||
return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
|
||||
}
|
||||
|
||||
|
|
@ -1227,11 +1215,12 @@ export const getSendableReasoningInfo = (
|
|||
providerName: ProviderName,
|
||||
modelName: string,
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined,
|
||||
overridesOfModel: OverridesOfModel | undefined,
|
||||
): SendableReasoningInfo => {
|
||||
|
||||
const { canIOReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {}
|
||||
const { canIOReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {}
|
||||
if (!canIOReasoning) return null
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel)
|
||||
if (!isReasoningEnabled) return null
|
||||
|
||||
// check for reasoning budget
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolName, ToolParamName } from './prompt/prompts.js'
|
||||
import { ChatMode, ModelSelection, ModelSelectionOptions, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js'
|
||||
import { ChatMode, ModelSelection, ModelSelectionOptions, OverridesOfModel, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js'
|
||||
|
||||
|
||||
export const errorDetails = (fullError: Error | null): string | null => {
|
||||
|
|
@ -116,6 +116,7 @@ export type ServiceSendLLMMessageParams = {
|
|||
logging: { loggingName: string, loggingExtras?: { [k: string]: any } };
|
||||
modelSelection: ModelSelection | null;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
overridesOfModel: OverridesOfModel | undefined;
|
||||
onAbort: OnAbort;
|
||||
} & SendLLMType;
|
||||
|
||||
|
|
@ -129,6 +130,7 @@ export type SendLLMMessageParams = {
|
|||
|
||||
modelSelection: ModelSelection;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
overridesOfModel: OverridesOfModel | undefined;
|
||||
|
||||
settingsOfProvider: SettingsOfProvider;
|
||||
} & SendLLMType
|
||||
|
|
|
|||
|
|
@ -96,8 +96,15 @@ const _modelsWithSwappedInNewModels = (options: { existingModels: VoidStatefulMo
|
|||
}
|
||||
|
||||
|
||||
export const modelFilterOfFeatureName: { [featureName in FeatureName]: { filter: (o: ModelSelection, opts: { chatMode: ChatMode }) => boolean; emptyMessage: null | { message: string, priority: 'always' | 'fallback' } } } = {
|
||||
'Autocomplete': { filter: (o) => getModelCapabilities(o.providerName, o.modelName).supportsFIM, emptyMessage: { message: 'No models support FIM', priority: 'always' } },
|
||||
export const modelFilterOfFeatureName: {
|
||||
[featureName in FeatureName]: {
|
||||
filter: (
|
||||
o: ModelSelection,
|
||||
opts: { chatMode: ChatMode, overridesOfModel: OverridesOfModel }
|
||||
) => boolean;
|
||||
emptyMessage: null | { message: string, priority: 'always' | 'fallback' }
|
||||
} } = {
|
||||
'Autocomplete': { filter: (o, opts) => getModelCapabilities(o.providerName, o.modelName, opts.overridesOfModel).supportsFIM, emptyMessage: { message: 'No models support FIM', priority: 'always' } },
|
||||
'Chat': { filter: o => true, emptyMessage: null, },
|
||||
'Ctrl+K': { filter: o => true, emptyMessage: null, },
|
||||
'Apply': { filter: o => true, emptyMessage: null, },
|
||||
|
|
@ -165,7 +172,7 @@ const _validatedModelState = (state: Omit<VoidSettingsState, '_modelOptions'>):
|
|||
for (const featureName of featureNames) {
|
||||
|
||||
const { filter } = modelFilterOfFeatureName[featureName]
|
||||
const filterOpts = { chatMode: state.globalSettings.chatMode }
|
||||
const filterOpts = { chatMode: state.globalSettings.chatMode, overridesOfModel: state.overridesOfModel }
|
||||
const modelOptionsForThisFeature = newModelOptions.filter((o) => filter(o.selection, filterOpts))
|
||||
|
||||
const modelSelectionAtFeature = newModelSelectionOfFeature[featureName]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { GoogleAuth } from 'google-auth-library'
|
|||
/* eslint-enable */
|
||||
|
||||
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
|
||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js';
|
||||
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
|
||||
import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js';
|
||||
|
|
@ -39,6 +39,7 @@ type InternalCommonMessageParams = {
|
|||
providerName: ProviderName;
|
||||
settingsOfProvider: SettingsOfProvider;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
overridesOfModel: OverridesOfModel | undefined;
|
||||
modelName: string;
|
||||
_setAborter: (aborter: () => void) => void;
|
||||
}
|
||||
|
|
@ -144,9 +145,9 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
|
|||
}
|
||||
|
||||
|
||||
const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, }: SendFIMParams_Internal) => {
|
||||
const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, overridesOfModel }: SendFIMParams_Internal) => {
|
||||
|
||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
||||
if (!supportsFIM) {
|
||||
if (modelName === modelName_)
|
||||
onError({ message: `Model ${modelName} does not support FIM.`, fullError: null })
|
||||
|
|
@ -230,18 +231,18 @@ const rawToolCallObjOf = (name: string, toolParamsStr: string, id: string): RawT
|
|||
// ------------ OPENAI-COMPATIBLE ------------
|
||||
|
||||
|
||||
const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage }: SendChatParams_Internal) => {
|
||||
const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage, overridesOfModel }: SendChatParams_Internal) => {
|
||||
const {
|
||||
modelName,
|
||||
specialToolFormat,
|
||||
reasoningCapabilities,
|
||||
} = getModelCapabilities(providerName, modelName_)
|
||||
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
||||
|
||||
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
||||
|
||||
// reasoning
|
||||
const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {}
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||
|
||||
// tools
|
||||
|
|
@ -415,21 +416,21 @@ const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBloc
|
|||
}
|
||||
|
||||
// ------------ ANTHROPIC ------------
|
||||
const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => {
|
||||
const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => {
|
||||
const {
|
||||
modelName,
|
||||
specialToolFormat,
|
||||
} = getModelCapabilities(providerName, modelName_)
|
||||
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
||||
|
||||
const thisConfig = settingsOfProvider.anthropic
|
||||
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
||||
|
||||
// reasoning
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||
|
||||
// anthropic-specific - max tokens
|
||||
const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled })
|
||||
const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled, overridesOfModel })
|
||||
|
||||
// tools
|
||||
const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null
|
||||
|
|
@ -539,8 +540,8 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag
|
|||
|
||||
// ------------ MISTRAL ------------
|
||||
// https://docs.mistral.ai/api/#tag/fim
|
||||
const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName }: SendFIMParams_Internal) => {
|
||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
||||
const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, overridesOfModel, modelName: modelName_, _setAborter, providerName }: SendFIMParams_Internal) => {
|
||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
||||
if (!supportsFIM) {
|
||||
if (modelName === modelName_)
|
||||
onError({ message: `Model ${modelName} does not support FIM.`, fullError: null })
|
||||
|
|
@ -679,6 +680,7 @@ const sendGeminiChat = async ({
|
|||
onFinalMessage,
|
||||
onError,
|
||||
settingsOfProvider,
|
||||
overridesOfModel,
|
||||
modelName: modelName_,
|
||||
_setAborter,
|
||||
providerName,
|
||||
|
|
@ -694,13 +696,13 @@ const sendGeminiChat = async ({
|
|||
modelName,
|
||||
specialToolFormat,
|
||||
// reasoningCapabilities,
|
||||
} = getModelCapabilities(providerName, modelName_)
|
||||
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
|
||||
|
||||
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
||||
|
||||
// reasoning
|
||||
// const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {}
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||
|
||||
// tools
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const sendLLMMessage = async ({
|
|||
settingsOfProvider,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
chatMode,
|
||||
separateSystemMessage,
|
||||
}: SendLLMMessageParams,
|
||||
|
|
@ -106,12 +107,12 @@ export const sendLLMMessage = async ({
|
|||
}
|
||||
const { sendFIM, sendChat } = implementation
|
||||
if (messagesType === 'chatMessages') {
|
||||
await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage, chatMode })
|
||||
await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName, _setAborter, providerName, separateSystemMessage, chatMode })
|
||||
return
|
||||
}
|
||||
if (messagesType === 'FIMMessage') {
|
||||
if (sendFIM) {
|
||||
await sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage })
|
||||
await sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName, _setAborter, providerName, separateSystemMessage })
|
||||
return
|
||||
}
|
||||
onError({ message: `Error running Autocomplete with ${providerName} - ${modelName}.`, fullError: null })
|
||||
|
|
|
|||
Loading…
Reference in a new issue