Merge pull request #398 from voideditor/model-selection

GPT 4.1 + fixes
This commit is contained in:
Andrew Pareles 2025-04-14 13:34:33 -07:00 committed by GitHub
commit fe017948c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 138 additions and 31 deletions

View file

@ -304,12 +304,6 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
return <p>{contents}</p>
}
if (t.type === "html") {
const contents = t.raw
if (inPTag) return <span className='block'>{contents}</span>
return <p>{contents}</p>
}
if (t.type === "text" || t.type === "escape") {
return <span>{t.raw}</span>
}
@ -324,7 +318,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
onClick={() => { window.open(t.href) }}
href={t.href}
title={t.title ?? undefined}
className='underline cursor-pointer hover:brightness-90 transition-all duration-200'
className='underline cursor-pointer hover:brightness-90 transition-all duration-200 text-void-fg-2'
>
{t.text}
</a>
@ -349,7 +343,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
}
// inline code
if (t.type === "codespan") {
if (t.type === "codespan" || t.type === "html") {
if (options.isLinkDetectionEnabled && chatMessageLocation) {
return <CodespanWithLink

View file

@ -26,7 +26,6 @@ import { LintErrorItem, ToolCallParams, ToolNameWithApproval } from '../../../..
import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
import { IsRunningType } from '../../../chatThreadService.js';
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
import { PlacesType } from 'react-tooltip';
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
import { error } from 'console';
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
@ -662,7 +661,8 @@ type ToolHeaderParams = {
children?: React.ReactNode;
bottomChildren?: React.ReactNode;
onClick?: () => void;
isOpen?: boolean,
isOpen?: boolean;
className?: string;
}
const ToolHeaderWrapper = ({
@ -679,7 +679,7 @@ const ToolHeaderWrapper = ({
isOpen,
isRejected,
className, // applies to the main content
}: ToolHeaderParams & { className?: string }) => {
}: ToolHeaderParams) => {
const [isOpen_, setIsOpen] = useState(false);
const isExpanded = isOpen !== undefined ? isOpen : isOpen_
@ -1176,7 +1176,8 @@ const titleOfToolName = {
'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) },
'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) },
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
'run_terminal_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') }
'run_terminal_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') },
} as const satisfies Record<ToolName, { done: any, proposed: any, running: any }>
const getTitle = (toolMessage: Pick<ChatMessage & { role: 'tool' }, 'name' | 'type'>): React.ReactNode => {
@ -1343,6 +1344,15 @@ const EditToolChildren = ({ uri, changeDescription }: { uri: URI | undefined, ch
</div>
}
const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
return <div className="text-xs text-void-fg-4 opacity-80 border-l-2 border-void-warning px-2 py-0.5 flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap">
{lintErrors.map((error, i) => (
<div key={i}>Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
))}
</div>
}
const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
if (lintErrors.length === 0) return null;
@ -1352,11 +1362,7 @@ const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) =>
return (
<div className="w-full px-2">
<ToolHeaderWrapper className='!border-t-0' title={'Lint errors'} desc1={''} isOpen={isOpen} onClick={() => { setIsOpen(o => !o) }} >
<div className="text-xs text-void-fg-4 opacity-80 border-l-2 border-void-warning px-2 py-0.5 flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap">
{lintErrors.map((error, i) => (
<div key={i}>Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
))}
</div>
<LintErrorChildren lintErrors={lintErrors} />
</ToolHeaderWrapper>
</div>
@ -1427,6 +1433,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const isError = toolMessage.type === 'tool_error'
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) {
const start = toolMessage.params.startLine === null ? `start` : `${toolMessage.params.startLine}`
const end = toolMessage.params.endLine === null ? `end` : `${toolMessage.params.endLine}`
const addStr = `(${start}-${end})`
componentParams.title += ` ${addStr}`
}
if (toolMessage.type === 'success') {
const { params, result } = toolMessage
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
@ -1626,6 +1639,47 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
}
},
'read_lint_errors': {
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const title = getTitle(toolMessage)
const { uri } = toolMessage.params ?? {}
const desc1 = uri ? getBasename(uri.fsPath) : '';
const icon = null
if (toolMessage.type === 'tool_request') return null
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
if (toolMessage.type === 'running_now') return null // do not show running
const isError = toolMessage.type === 'tool_error'
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
if (toolMessage.type === 'success') {
const { params, result } = toolMessage
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
if (result.lintErrors)
componentParams.children = <LintErrorChildren lintErrors={result.lintErrors} />
else
componentParams.children = `No lint errors found.`
}
else if (toolMessage.type === 'tool_error') {
const { params, result } = toolMessage
if (params) componentParams.desc2 = <JumpToFileButton uri={params.uri} />
componentParams.children = <ToolChildrenWrapper>
<CodeChildren>
{result}
</CodeChildren>
</ToolChildrenWrapper>
}
return <ToolHeaderWrapper {...componentParams} />
},
},
// ---
'create_file_or_folder': {

View file

@ -51,11 +51,14 @@ export const defaultProviderSettings = {
export const defaultModelsOfProvider = {
openAI: [ // https://platform.openai.com/docs/models/gp
'gpt-4.1',
'gpt-4.1-mini',
'gpt-4.1-nano',
'o3-mini',
'o1',
'o1-mini',
'gpt-4o',
'gpt-4o-mini',
// 'o1',
// 'o1-mini',
// 'gpt-4o',
// 'gpt-4o-mini',
],
anthropic: [ // https://docs.anthropic.com/en/docs/about-claude/models
'claude-3-7-sonnet-latest',
@ -344,12 +347,16 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (
if (lower.includes('quasar') || lower.includes('quaser')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['quasar'] })
if (lower.includes('gpt') && lower.includes('mini') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions['gpt-4.1-mini'])
if (lower.includes('gpt') && lower.includes('nano') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions['gpt-4.1-nano'])
if (lower.includes('gpt') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions['gpt-4.1'])
if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions['gpt-4o-mini'])
if (lower.includes('4o')) return toFallback(openAIModelOptions['gpt-4o'])
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions['o1-mini'])
if (lower.includes('o1')) return toFallback(openAIModelOptions['o1'])
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions['o3-mini'])
// if (lower.includes('o3')) return toFallback(openAIModelOptions['o3'])
if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower))
return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat])
@ -444,6 +451,33 @@ const anthropicSettings: VoidStaticProviderInfo = {
// ---------------- OPENAI ----------------
const openAIModelOptions = { // https://platform.openai.com/docs/pricing
'gpt-4.1': {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
cost: { input: 2.00, output: 8.00, cache_read: 0.50 },
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'developer-role',
reasoningCapabilities: false,
},
'gpt-4.1-mini': {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
cost: { input: 0.40, output: 1.60, cache_read: 0.10 },
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'developer-role',
reasoningCapabilities: false,
},
'gpt-4.1-nano': {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
cost: { input: 0.10, output: 0.40, cache_read: 0.03 },
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'developer-role',
reasoningCapabilities: false,
},
'o1': {
contextWindow: 128_000,
maxOutputTokens: 100_000,

View file

@ -241,8 +241,8 @@ ${availableXMLToolsStr(tools)}`)
const toolCallXMLGuidelines = (`\
Tool calling details:
- Once you write a tool call, you must STOP and WAIT for the result.
- To call a tool, write its name and parameters in one of the XML formats specified above at the BOTTOM of your response.
- All parameters are REQUIRED unless noted otherwise.
- To call a tool, write its name and parameters in one of the XML formats specified above.
- You are only allowed to output ONE tool call, and it must be at the END of your response.
- Your tool call will be executed immediately, and the results will appear in the following user message.`)

View file

@ -71,8 +71,8 @@ export interface IVoidSettingsService {
const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], options: { existingModels: VoidStatefulModelInfo[] }) => {
const { existingModels } = options
const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], options: { existingModels: VoidStatefulModelInfo[], didAutoDetect: boolean }) => {
const { existingModels, didAutoDetect } = options
const existingModelsMap: Record<string, VoidStatefulModelInfo> = {}
for (const existingModel of existingModels) {
@ -82,7 +82,7 @@ const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], opt
const newDefaultModels = defaultModelNames.map((modelName, i) => ({
modelName,
isDefault: true,
isAutodetected: true,
isAutodetected: didAutoDetect,
isHidden: !!existingModelsMap[modelName]?.isHidden,
}))
@ -101,7 +101,30 @@ export const modelFilterOfFeatureName: { [featureName in FeatureName]: { filter:
}
const _validatedModelState = (state: Omit<VoidSettingsState, '_modelOptions'>) => {
const _stateWithUpdatedDefaultModels = (state: VoidSettingsState): VoidSettingsState => {
let newSettingsOfProvider = state.settingsOfProvider
// recompute default models
for (const providerName of providerNames) {
const defaultModels = defaultSettingsOfProvider[providerName]?.models ?? []
const currentModels = newSettingsOfProvider[providerName]?.models ?? []
const defaultModelNames = defaultModels.map(m => m.modelName)
const newModels = _updatedModelsAfterDefaultModelsChange(defaultModelNames, { existingModels: currentModels, didAutoDetect: false })
newSettingsOfProvider = {
...newSettingsOfProvider,
[providerName]: {
...newSettingsOfProvider[providerName],
models: newModels,
},
}
}
return {
...state,
settingsOfProvider: newSettingsOfProvider,
}
}
const _validatedModelState = (state: Omit<VoidSettingsState, '_modelOptions'>): VoidSettingsState => {
let newSettingsOfProvider = state.settingsOfProvider
@ -222,8 +245,10 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
}
// the stored data structure might be outdated, so we need to update it here
const finalState = readS
this.state = _validatedModelState(finalState);
this.state = readS
this.state = _stateWithUpdatedDefaultModels(this.state)
this.state = _validatedModelState(this.state);
this._resolver();
this._onDidChangeState.fire();
@ -353,7 +378,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
const { models } = this.state.settingsOfProvider[providerName]
const oldModelNames = models.map(m => m.modelName)
const newModels = _updatedModelsAfterDefaultModelsChange(autodetectedModelNames, { existingModels: models })
const newModels = _updatedModelsAfterDefaultModelsChange(autodetectedModelNames, { existingModels: models, didAutoDetect: true })
this.setSettingOfProvider(providerName, 'models', newModels)
// if the models changed, log it

View file

@ -150,7 +150,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
providerName === 'groq' ? 'Get your [API Key here](https://console.groq.com/keys).' :
providerName === 'xAI' ? 'Get your [API Key here](https://console.x.ai).' :
providerName === 'mistral' ? 'Get your [API Key here](https://console.mistral.ai/api-keys).' :
providerName === 'openAICompatible' ? undefined :
providerName === 'openAICompatible' ? `Use any OpenAI-compatible endpoint (LM Studio, LiteLM, etc).` :
'',
isPasswordField: true,
}