Merge branch 'main' into void-settings-sidebar

This commit is contained in:
vrtnis 2025-05-23 11:27:39 -07:00 committed by GitHub
commit 23bb28f3c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 588 additions and 187 deletions

46
package-lock.json generated
View file

@ -299,13 +299,14 @@
"dev": true
},
"node_modules/@azure/core-auth": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz",
"integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz",
"integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-util": "^1.1.0",
"@azure/core-util": "^1.11.0",
"tslib": "^2.6.2"
},
"engines": {
@ -448,18 +449,18 @@
}
},
"node_modules/@azure/core-rest-pipeline": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.0.tgz",
"integrity": "sha512-CeuTvsXxCUmEuxH5g/aceuSl6w2EugvNHKAtKKVdiX915EjJJxAwfzNNWZreNnbxHZ2fi0zaM6wwS23x2JVqSQ==",
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.20.0.tgz",
"integrity": "sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.4.0",
"@azure/core-auth": "^1.8.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.9.0",
"@azure/core-util": "^1.11.0",
"@azure/logger": "^1.0.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"@typespec/ts-http-runtime": "^0.2.2",
"tslib": "^2.6.2"
},
"engines": {
@ -479,12 +480,14 @@
}
},
"node_modules/@azure/core-util": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz",
"integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==",
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.12.0.tgz",
"integrity": "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@typespec/ts-http-runtime": "^0.2.2",
"tslib": "^2.6.2"
},
"engines": {
@ -4257,6 +4260,21 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typespec/ts-http-runtime": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.2.tgz",
"integrity": "sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@vscode/deviceid": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@vscode/deviceid/-/deviceid-0.1.1.tgz",

View file

@ -1,8 +1,8 @@
{
"nameShort": "Void",
"nameLong": "Void",
"voidVersion": "1.3.6",
"voidRelease": "0030",
"voidVersion": "1.3.10",
"voidRelease": "0034",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",

View file

@ -418,6 +418,7 @@ const prepareOpenAIOrAnthropicMessages = ({
else {
// allowed to be empty if has a tool in it or following it
if (currMsg.content.find(c => c.type === 'tool_result' || c.type === 'tool_use')) {
currMsg.content = currMsg.content.filter(c => !(c.type === 'text' && !c.text)) as any
continue
}
if (nextMsg?.role === 'tool') continue

View file

@ -0,0 +1,78 @@
import { localize2 } from '../../../../nls.js';
import { URI } from '../../../../base/common/uri.js';
import { Action2, registerAction2, MenuId } from '../../../../platform/actions/common/actions.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IDirectoryStrService } from '../common/directoryStrService.js';
import { messageOfSelection } from '../common/prompt/prompts.js';
import { IVoidModelService } from '../common/voidModelService.js';
class FilePromptActionService extends Action2 {
private static readonly VOID_COPY_FILE_PROMPT_ID = 'void.copyfileprompt'
constructor() {
super({
id: FilePromptActionService.VOID_COPY_FILE_PROMPT_ID,
title: localize2('voidCopyPrompt', 'Void: Copy Prompt'),
menu: [{
id: MenuId.ExplorerContext,
group: '8_void',
order: 1,
}]
});
}
async run(accessor: ServicesAccessor, uri: URI): Promise<void> {
try {
const fileService = accessor.get(IFileService);
const clipboardService = accessor.get(IClipboardService)
const directoryStrService = accessor.get(IDirectoryStrService)
const voidModelService = accessor.get(IVoidModelService)
const stat = await fileService.stat(uri)
const folderOpts = {
maxChildren: 1000,
maxCharsPerFile: 2_000_000,
} as const
let m: string = 'No contents detected'
if (stat.isFile) {
m = await messageOfSelection({
type: 'File',
uri,
language: (await voidModelService.getModelSafe(uri)).model?.getLanguageId() || '',
state: { wasAddedAsCurrentFile: false, },
}, {
folderOpts,
directoryStrService,
fileService,
})
}
if (stat.isDirectory) {
m = await messageOfSelection({
type: 'Folder',
uri,
}, {
folderOpts,
fileService,
directoryStrService,
})
}
await clipboardService.writeText(m)
} catch (error) {
const notificationService = accessor.get(INotificationService)
notificationService.error(error + '')
}
}
}
registerAction2(FilePromptActionService)

View file

@ -8,6 +8,8 @@ import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase
import { IExtensionTransferService } from './extensionTransferService.js';
import { os } from '../common/helpers/systemInfo.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { timeout } from '../../../../base/common/async.js';
import { getActiveWindow } from '../../../../base/browser/dom.js';
// Onboarding contribution that mounts the component at startup
export class MiscWorkbenchContribs extends Disposable implements IWorkbenchContribution {
@ -31,6 +33,16 @@ export class MiscWorkbenchContribs extends Disposable implements IWorkbenchContr
this.extensionTransferService.deleteBlacklistExtensions(os)
}
// after some time, trigger a resize event for the blank screen error
timeout(5_000).then(() => {
// Get the active window reference for multi-window support
const targetWindow = getActiveWindow();
// Trigger a window resize event to ensure proper layout calculations
targetWindow.dispatchEvent(new Event('resize'))
})
}
}

View file

@ -13,7 +13,7 @@ import { roundRangeToLines } from './sidebarActions.js';
import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js';
import { localize2 } from '../../../../nls.js';
import { IMetricsService } from '../common/metricsService.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
export type QuickEditPropsType = {
diffareaid: number,
@ -42,6 +42,7 @@ registerAction2(class extends Action2 {
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.KeyK,
weight: KeybindingWeight.VoidExtension,
when: ContextKeyExpr.deserialize('editorFocus && !terminalFocus'),
}
});
}

View file

@ -239,17 +239,94 @@ export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId:
}
export const ApplyButtonsHTML = ({
const terminalLanguages = new Set([
'bash',
'shellscript',
'shell',
'powershell',
'bat',
'zsh',
'sh',
'fish',
'nushell',
'ksh',
'xonsh',
'elvish',
])
const ApplyButtonsForTerminal = ({
codeStr,
applyBoxId,
uri,
language,
}: {
codeStr: string,
applyBoxId: string,
} & ({
language?: string,
uri: URI | 'current';
})
) => {
}) => {
const accessor = useAccessor()
const metricsService = accessor.get('IMetricsService')
const terminalToolService = accessor.get('ITerminalToolService')
const settingsState = useSettingsState()
const [isShellRunning, setIsShellRunning] = useState<boolean>(false)
const interruptToolRef = useRef<(() => void) | null>(null)
const isDisabled = isShellRunning
const onClickSubmit = useCallback(async () => {
if (isShellRunning) return
try {
setIsShellRunning(true)
const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null })
const { interrupt } = await terminalToolService.runCommand(
codeStr,
{ type: 'persistent', persistentTerminalId: terminalId }
);
interruptToolRef.current = interrupt
metricsService.capture('Execute Shell', { length: codeStr.length })
} catch (e) {
setIsShellRunning(false)
console.error('Failed to execute in terminal:', e)
}
}, [codeStr, uri, applyBoxId, metricsService, terminalToolService, isShellRunning])
if (isShellRunning) {
return (
<IconShell1
Icon={X}
onClick={() => {
interruptToolRef.current?.();
setIsShellRunning(false);
}}
{...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })}
/>
);
}
if (isDisabled) {
return null
}
return <IconShell1
Icon={Play}
onClick={onClickSubmit}
{...tooltipPropsForApplyBlock({ tooltipName: 'Apply' })}
/>
}
const ApplyButtonsForEdit = ({
codeStr,
applyBoxId,
uri,
language,
}: {
codeStr: string,
applyBoxId: string,
language?: string,
uri: URI | 'current';
}) => {
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const metricsService = accessor.get('IMetricsService')
@ -260,7 +337,6 @@ export const ApplyButtonsHTML = ({
const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId })
const onClickSubmit = useCallback(async () => {
if (currStreamStateRef.current === 'streaming') return
@ -287,7 +363,7 @@ export const ApplyButtonsHTML = ({
})
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
}, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService])
}, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, notificationService])
const onClickStop = useCallback(() => {
@ -309,9 +385,7 @@ export const ApplyButtonsHTML = ({
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'reject', removeCtrlKs: false })
}, [uri, applyBoxId, editCodeService])
const currStreamState = currStreamStateRef.current
if (currStreamState === 'streaming') {
return <IconShell1
Icon={Square}
@ -319,12 +393,9 @@ export const ApplyButtonsHTML = ({
{...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })}
/>
}
if (isDisabled) {
return null
}
if (currStreamState === 'idle-no-changes') {
return <IconShell1
Icon={Play}
@ -332,7 +403,6 @@ export const ApplyButtonsHTML = ({
{...tooltipPropsForApplyBlock({ tooltipName: 'Apply' })}
/>
}
if (currStreamState === 'idle-has-changes') {
return <Fragment>
<IconShell1
@ -353,6 +423,27 @@ export const ApplyButtonsHTML = ({
export const ApplyButtonsHTML = (params: {
codeStr: string,
applyBoxId: string,
language?: string,
uri: URI | 'current';
}) => {
const { language } = params
const isShellLanguage = !!language && terminalLanguages.has(language)
if (isShellLanguage) {
return <ApplyButtonsForTerminal {...params} />
}
else {
return <ApplyButtonsForEdit {...params} />
}
}
export const EditToolAcceptRejectButtonsHTML = ({
codeStr,
applyBoxId,
@ -456,7 +547,7 @@ export const BlockCodeApplyWrapper = ({
<div className={`${canApply ? '' : 'hidden'} flex items-center gap-1`}>
<JumpToFileButton uri={uri} />
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} toolTipName='Copy' />}
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={codeStr} />
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={codeStr} language={language} />
</div>
</div>

View file

@ -2605,7 +2605,7 @@ const CommandBarInChat = () => {
// !select-text cursor-auto
const fileDetailsContent = <div className="px-2 gap-1 w-full">
const fileDetailsContent = <div className="px-2 gap-1 w-full overflow-y-auto">
{sortedCommandBarURIs.map((uri, i) => {
const basename = getBasename(uri.fsPath)
@ -2862,6 +2862,7 @@ export const SidebarChat = () => {
textAreaRef: textAreaRef,
scrollToBottom: () => scrollToBottom(scrollContainerRef),
})
}, [chatThreadsState, threadId, textAreaRef, scrollContainerRef, isResolved])

View file

@ -17,7 +17,7 @@ import { os } from '../../../../common/helpers/systemInfo.js'
import { IconLoading } from '../sidebar-tsx/SidebarChat.js'
import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js'
import Severity from '../../../../../../../base/common/severity.js'
import { getModelCapabilities, ModelOverrides } from '../../../../common/modelCapabilities.js';
import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../../../common/modelCapabilities.js';
import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js';
// ─────────────────────────────────────────────
// Sidebar navigation helpers
@ -195,6 +195,11 @@ const ConfirmButton = ({ children, onConfirm, className }: { children: React.Rea
};
// ---------------- Simplified Model Settings Dialog ------------------
// keys of ModelOverrides we allow the user to override
// This new dialog replaces the verbose UI with a single JSON override box.
const SimpleModelSettingsDialog = ({
isOpen,
@ -218,39 +223,26 @@ const SimpleModelSettingsDialog = ({
const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] ?? undefined;
const { recognizedModelName, isUnrecognizedModel } = defaultModelCapabilities
// keys of ModelOverrides we allow the user to override
const allowedKeys: (string & (keyof ModelOverrides))[] = [
'contextWindow',
'reservedOutputTokenSpace',
'supportsSystemMessage',
'specialToolFormat',
'supportsFIM',
'reasoningCapabilities',
];
// Create the placeholder with the default values for allowed keys
const partialDefaults: Partial<ModelOverrides> = {};
for (const k of allowedKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; }
for (const k of modelOverrideKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; }
const placeholder = JSON.stringify(partialDefaults, null, 2);
const [overrideEnabled, setOverrideEnabled] = useState<boolean>(() => !!currentOverrides);
const [jsonText, setJsonText] = useState<string>(() => currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder);
const [readOnlyHeight, setReadOnlyHeight] = useState<number | undefined>(undefined);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
// reset when dialog toggles
useEffect(() => {
if (!isOpen) return;
const cur = settingsState.overridesOfModel?.[providerName]?.[modelName];
setOverrideEnabled(!!cur);
// If there are overrides, show them; otherwise use default values
setJsonText(cur ? JSON.stringify(cur, null, 2) : placeholder);
setErrorMsg(null);
}, [isOpen, providerName, modelName, settingsState.overridesOfModel, placeholder]);
const onSave = async () => {
// if disabled override, reset overrides
if (!overrideEnabled) {
await settingsStateService.setOverridesOfModel(providerName, modelName, undefined);
@ -261,9 +253,10 @@ const SimpleModelSettingsDialog = ({
// enabled overrides
// parse json
let parsedInput: Record<string, unknown>
if (jsonText.trim()) {
if (textAreaRef.current?.value) {
try {
parsedInput = JSON.parse(jsonText);
parsedInput = JSON.parse(textAreaRef.current.value);
} catch (e) {
setErrorMsg('Invalid JSON');
return;
@ -275,10 +268,10 @@ const SimpleModelSettingsDialog = ({
// only keep allowed keys
const cleaned: Partial<ModelOverrides> = {};
for (const k of allowedKeys) {
for (const k of modelOverrideKeys) {
if (!(k in parsedInput)) continue
const isEmpty = parsedInput[k] === '' || parsedInput[k] === null || parsedInput[k] === undefined;
if (!isEmpty && (k in partialDefaults)) {
if (!isEmpty) {
cleaned[k] = parsedInput[k] as any;
}
}
@ -286,7 +279,7 @@ const SimpleModelSettingsDialog = ({
onClose();
};
const sourcecodeOverridesLink = `https://github.com/voideditor/void/blob/d33b5ff9a32a748a22ac99e543a9eedd2e600062/src/vs/workbench/contrib/void/common/modelCapabilities.ts#L146-L170`
const sourcecodeOverridesLink = `https://github.com/voideditor/void/blob/2e5ecb291d33afbe4565921664fb7e183189c1c5/src/vs/workbench/contrib/void/common/modelCapabilities.ts#L146-L172`
return (
<div // Backdrop
@ -343,10 +336,11 @@ const SimpleModelSettingsDialog = ({
</div>}
<textarea
key={overrideEnabled + ''}
ref={textAreaRef}
className={`w-full min-h-[200px] p-2 rounded-sm border border-void-border-2 bg-void-bg-2 resize-none font-mono text-sm ${!overrideEnabled ? 'text-void-fg-3' : ''}`}
value={overrideEnabled ? jsonText : placeholder}
defaultValue={overrideEnabled && currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder}
placeholder={placeholder}
onChange={overrideEnabled ? (e) => setJsonText(e.target.value) : undefined}
readOnly={!overrideEnabled}
/>
{errorMsg && (

View file

@ -89,10 +89,7 @@ registerAction2(class extends Action2 {
});
}
async run(accessor: ServicesAccessor): Promise<void> {
// Get the views service to check if the sidebar is open
// const viewsService = accessor.get(IViewsService)
// Get services
const commandService = accessor.get(ICommandService)
const viewsService = accessor.get(IViewsService)
const metricsService = accessor.get(IMetricsService)
@ -101,31 +98,28 @@ registerAction2(class extends Action2 {
metricsService.capture('Ctrl+L', {})
// capture selection and model before opening the chat panel
const editor = editorService.getActiveCodeEditor()
const model = editor?.getModel()
if (!model) return
const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' })
// open panel
const wasAlreadyOpen = viewsService.isViewContainerVisible(VOID_VIEW_CONTAINER_ID)
if (!wasAlreadyOpen) {
await commandService.executeCommand(VOID_OPEN_SIDEBAR_ACTION_ID)
return
}
// if was already open
const model = accessor.get(ICodeEditorService).getActiveCodeEditor()?.getModel()
if (!model) return
const editor = editorService.getActiveCodeEditor()
const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' })
// if has no selection, close + return
// if (!selectionRange) {
// viewsService.closeViewContainer(VOID_VIEW_CONTAINER_ID);
// return;
// }
// Add selection to chat
// add line selection
if (selectionRange) {
editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER })
editor?.setSelection({
startLineNumber: selectionRange.startLineNumber,
endLineNumber: selectionRange.endLineNumber,
startColumn: 1,
endColumn: Number.MAX_SAFE_INTEGER
})
chatThreadService.addNewStagingSelection({
type: 'CodeSelection',
uri: model.uri,
@ -142,12 +136,9 @@ registerAction2(class extends Action2 {
language: model.getLanguageId(),
state: { wasAddedAsCurrentFile: false },
})
}
await chatThreadService.focusCurrentChat()
}
})

View file

@ -22,7 +22,12 @@ export interface ITerminalToolService {
readonly _serviceBrand: undefined;
listPersistentTerminalIds(): string[];
runCommand(command: string, opts: { type: 'persistent', persistentTerminalId: string } | { type: 'ephemeral', cwd: string | null, terminalId: string }): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>;
runCommand(command: string, opts:
| { type: 'persistent', persistentTerminalId: string }
| { type: 'temporary', cwd: string | null, terminalId: string }
// | { type: 'apply', terminalId: string }
): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>;
focusPersistentTerminal(terminalId: string): Promise<void>
persistentTerminalExists(terminalId: string): boolean
@ -277,6 +282,8 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
terminal.dispose()
if (!isPersistent)
delete this.temporaryTerminalInstanceOfId[params.terminalId]
else
delete this.persistentTerminalInstanceOfId[params.persistentTerminalId]
}
const waitForResult = async () => {

View file

@ -430,7 +430,7 @@ export class ToolsService implements IToolsService {
},
// ---
run_command: async ({ command, cwd, terminalId }) => {
const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'ephemeral', cwd, terminalId })
const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'temporary', cwd, terminalId })
return { result: resPromise, interruptTool: interrupt }
},
run_persistent_command: async ({ command, persistentTerminalId }) => {

View file

@ -58,6 +58,9 @@ import './voidOnboardingService.js'
// register misc service
import './miscWokrbenchContrib.js'
// register file service (for explorer context menu)
import './fileService.js'
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
// llmMessage

View file

@ -8,7 +8,7 @@ import Severity from '../../../../base/common/severity.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../nls.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { INotificationActions, INotificationService } from '../../../../platform/notification/common/notification.js';
import { INotificationActions, INotificationHandle, INotificationService } from '../../../../platform/notification/common/notification.js';
import { IMetricsService } from '../common/metricsService.js';
import { IVoidUpdateService } from '../common/voidUpdateService.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
@ -20,7 +20,7 @@ import { IAction } from '../../../../base/common/actions.js';
const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService) => {
const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService): INotificationHandle => {
const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!'
let actions: INotificationActions | undefined
@ -119,18 +119,21 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
progress: actions ? { worked: 0, total: 100 } : undefined,
actions: actions,
})
return notifController
// const d = notifController.onDidClose(() => {
// notifyYesUpdate(notifService, res)
// d.dispose()
// })
}
const notifyErrChecking = (notifService: INotificationService) => {
const notifyErrChecking = (notifService: INotificationService): INotificationHandle => {
const message = `Void Error: There was an error checking for updates. If this persists, please get in touch or reinstall Void [here](https://voideditor.com/download-beta)!`
notifService.notify({
const notifController = notifService.notify({
severity: Severity.Info,
message: message,
sticky: true,
})
return notifController
}
@ -140,30 +143,35 @@ const performVoidCheck = async (
voidUpdateService: IVoidUpdateService,
metricsService: IMetricsService,
updateService: IUpdateService,
) => {
): Promise<INotificationHandle | null> => {
const metricsTag = explicit ? 'Manual' : 'Auto'
metricsService.capture(`Void Update ${metricsTag}: Checking...`, {})
const res = await voidUpdateService.check(explicit)
if (!res) {
notifyErrChecking(notifService);
const notifController = notifyErrChecking(notifService);
metricsService.capture(`Void Update ${metricsTag}: Error`, { res })
return notifController
}
else {
if (res.message) {
notifyUpdate(res, notifService, updateService)
const notifController = notifyUpdate(res, notifService, updateService)
metricsService.capture(`Void Update ${metricsTag}: Yes`, { res })
return notifController
}
else {
metricsService.capture(`Void Update ${metricsTag}: No`, { res })
return
return null
}
}
}
// Action
let lastNotifController: INotificationHandle | null = null
registerAction2(class extends Action2 {
constructor() {
super({
@ -177,7 +185,15 @@ registerAction2(class extends Action2 {
const notifService = accessor.get(INotificationService)
const metricsService = accessor.get(IMetricsService)
const updateService = accessor.get(IUpdateService)
performVoidCheck(true, notifService, voidUpdateService, metricsService, updateService)
const currNotifController = lastNotifController
const newController = await performVoidCheck(true, notifService, voidUpdateService, metricsService, updateService)
if (newController) {
currNotifController?.close()
lastNotifController = newController
}
}
})

View file

@ -78,14 +78,16 @@ export const defaultModelsOfProvider = {
// 'gpt-4o-mini',
],
anthropic: [ // https://docs.anthropic.com/en/docs/about-claude/models
'claude-opus-4-0',
'claude-sonnet-4-0',
'claude-3-7-sonnet-latest',
'claude-3-5-sonnet-latest',
'claude-3-5-haiku-latest',
'claude-3-opus-latest',
],
xAI: [ // https://docs.x.ai/docs/models?cluster=us-east-1
'grok-2-latest',
'grok-3-latest',
'grok-2',
'grok-3',
],
gemini: [ // https://ai.google.dev/gemini-api/docs/models/gemini
'gemini-2.5-pro-exp-03-25',
@ -106,11 +108,14 @@ export const defaultModelsOfProvider = {
openRouter: [ // https://openrouter.ai/models
// 'anthropic/claude-3.7-sonnet:thinking',
'anthropic/claude-opus-4',
'anthropic/claude-sonnet-4',
'qwen/qwen3-235b-a22b',
'anthropic/claude-3.7-sonnet',
'anthropic/claude-3.5-sonnet',
'deepseek/deepseek-r1',
'deepseek/deepseek-r1-zero:free',
'mistralai/devstral-small:free'
// 'openrouter/quasar-alpha',
// 'google/gemini-2.5-pro-preview-03-25',
// 'mistralai/codestral-2501',
@ -128,6 +133,7 @@ export const defaultModelsOfProvider = {
],
mistral: [ // https://docs.mistral.ai/getting-started/models/models_overview/
'codestral-latest',
'devstral-small-latest',
'mistral-large-latest',
'mistral-medium-latest',
'ministral-3b-latest',
@ -154,6 +160,8 @@ export type VoidStaticModelInfo = { // not stateful
specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style', // typically you should use 'openai-style'. null means "can't call tools by default", and asks the LLM to output XML in agent mode
supportsFIM: boolean; // whether the model was specifically designed for autocomplete or "FIM" ("fill-in-middle" format)
additionalOpenAIPayload?: { [key: string]: string } // additional payload in the message body for requests that are openai-compatible (ollama, vllm, openai, openrouter, etc)
// reasoning options
reasoningCapabilities: false | {
readonly supportsReasoning: true; // for clarity, this must be true if anything below is specified
@ -186,8 +194,20 @@ export type VoidStaticModelInfo = { // not stateful
// if you change the above type, remember to update the Settings link
export type ModelOverrides = Pick<VoidStaticModelInfo,
'contextWindow' | 'reservedOutputTokenSpace' | 'specialToolFormat' | 'supportsSystemMessage' | 'supportsFIM' | 'reasoningCapabilities'
export const modelOverrideKeys = [
'contextWindow',
'reservedOutputTokenSpace',
'supportsSystemMessage',
'specialToolFormat',
'supportsFIM',
'reasoningCapabilities',
'additionalOpenAIPayload'
] as const
export type ModelOverrides = Pick<
VoidStaticModelInfo,
(typeof modelOverrideKeys)[number]
>
@ -249,6 +269,12 @@ const openSourceModelOptions_assumingOAICompat = {
reasoningCapabilities: false,
contextWindow: 32_000, reservedOutputTokenSpace: 4_096,
},
'devstral': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
contextWindow: 131_000, reservedOutputTokenSpace: 8_192,
},
'openhands-lm-32b': { // https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -363,22 +389,28 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
: VoidStaticModelInfo & { modelName: string, recognizedModelName: string } => {
const opts = obj[recognizedModelName]
const supportsSystemMessage = opts.supportsSystemMessage === 'separated'
? 'system-role'
: opts.supportsSystemMessage
return {
recognizedModelName,
modelName,
...opts,
supportsSystemMessage: opts.supportsSystemMessage ? 'system-role' : false,
supportsSystemMessage: supportsSystemMessage,
cost: { input: 0, output: 0 },
downloadable: false,
...fallbackKnownValues
}
};
}
if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) return toFallback(geminiModelOptions, 'gemini-2.5-pro-exp-03-25')
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions, 'claude-3-5-sonnet-20241022')
if (lower.includes('claude')) return toFallback(anthropicModelOptions, 'claude-3-7-sonnet-20250219')
if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-2')
if (lower.includes('grok2') || lower.includes('grok2')) return toFallback(xAIModelOptions, 'grok-2')
if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-3')
if (lower.includes('deepseek-r1') || lower.includes('deepseek-reasoner')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekR1')
if (lower.includes('deepseek') && lower.includes('v2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV2')
@ -398,6 +430,7 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
if (lower.includes('qwq')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'qwq') }
if (lower.includes('phi4')) return toFallback(openSourceModelOptions_assumingOAICompat, 'phi4')
if (lower.includes('codestral')) return toFallback(openSourceModelOptions_assumingOAICompat, 'codestral')
if (lower.includes('devstral')) return toFallback(openSourceModelOptions_assumingOAICompat, 'devstral')
if (lower.includes('gemma')) return toFallback(openSourceModelOptions_assumingOAICompat, 'gemma')
@ -450,6 +483,40 @@ const anthropicModelOptions = {
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000. we cap at 8192 because above is typically not necessary (often even buggy)
},
},
'claude-opus-4-20250514': {
contextWindow: 200_000,
reservedOutputTokenSpace: 8_192,
cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 30.00 },
downloadable: false,
supportsFIM: false,
specialToolFormat: 'anthropic-style',
supportsSystemMessage: 'separated',
reasoningCapabilities: {
supportsReasoning: true,
canTurnOffReasoning: true,
canIOReasoning: true,
reasoningReservedOutputTokenSpace: 8192, // can bump it to 128_000 with beta mode output-128k-2025-02-19
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000. we cap at 8192 because above is typically not necessary (often even buggy)
},
},
'claude-sonnet-4-20250514': {
contextWindow: 200_000,
reservedOutputTokenSpace: 8_192,
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 6.00 },
downloadable: false,
supportsFIM: false,
specialToolFormat: 'anthropic-style',
supportsSystemMessage: 'separated',
reasoningCapabilities: {
supportsReasoning: true,
canTurnOffReasoning: true,
canIOReasoning: true,
reasoningReservedOutputTokenSpace: 8192, // can bump it to 128_000 with beta mode output-128k-2025-02-19
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000. we cap at 8192 because above is typically not necessary (often even buggy)
},
},
'claude-3-5-sonnet-20241022': {
contextWindow: 200_000,
@ -509,6 +576,10 @@ const anthropicSettings: VoidStaticProviderInfo = {
modelOptionsFallback: (modelName) => {
const lower = modelName.toLowerCase()
let fallbackName: keyof typeof anthropicModelOptions | null = null
if (lower.includes('claude-4-opus') || lower.includes('claude-opus-4')) fallbackName = 'claude-opus-4-20250514'
if (lower.includes('claude-4-sonnet') || lower.includes('claude-sonnet-4')) fallbackName = 'claude-sonnet-4-20250514'
if (lower.includes('claude-3-7-sonnet')) fallbackName = 'claude-3-7-sonnet-20250219'
if (lower.includes('claude-3-5-sonnet')) fallbackName = 'claude-3-5-sonnet-20241022'
if (lower.includes('claude-3-5-haiku')) fallbackName = 'claude-3-5-haiku-20241022'
@ -622,6 +693,16 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
} as const satisfies { [s: string]: VoidStaticModelInfo }
// https://platform.openai.com/docs/guides/reasoning?api-mode=chat
const openAICompatIncludeInPayloadReasoning = (reasoningInfo: SendableReasoningInfo) => {
if (!reasoningInfo?.isReasoningEnabled) return null
if (reasoningInfo.type === 'effort_slider_value') {
return { reasoning_effort: reasoningInfo.reasoningEffort }
}
return null
}
const openAISettings: VoidStaticProviderInfo = {
modelOptions: openAIModelOptions,
modelOptionsFallback: (modelName) => {
@ -634,17 +715,7 @@ const openAISettings: VoidStaticProviderInfo = {
return null
},
providerReasoningIOSettings: {
input: {
// https://platform.openai.com/docs/guides/reasoning?api-mode=chat
includeInPayload: (reasoningInfo) => {
if (!reasoningInfo?.isReasoningEnabled) return null
if (reasoningInfo.type === 'effort_slider_value') {
return { reasoning_effort: reasoningInfo.reasoningEffort }
}
return null
}
},
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
@ -659,6 +730,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: false,
},
'grok-3': {
@ -668,6 +740,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: false,
},
'grok-3-fast': {
@ -677,6 +750,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: false,
},
// only mini supports thinking
@ -687,6 +761,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } },
},
'grok-3-mini-fast': {
@ -696,6 +771,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } },
},
} as const satisfies { [s: string]: VoidStaticModelInfo }
@ -706,11 +782,15 @@ const xAISettings: VoidStaticProviderInfo = {
const lower = modelName.toLowerCase()
let fallbackName: keyof typeof xAIModelOptions | null = null
if (lower.includes('grok-2')) fallbackName = 'grok-2'
if (lower.includes('grok-3')) fallbackName = 'grok-3'
if (lower.includes('grok')) fallbackName = 'grok-3'
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...xAIModelOptions[fallbackName] }
return null
},
// same implementation as openai
providerReasoningIOSettings: openAISettings.providerReasoningIOSettings,
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
@ -783,13 +863,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
supportsFIM: false,
supportsSystemMessage: 'separated',
specialToolFormat: 'gemini-style',
reasoningCapabilities: { // thinking: experimental as of 5-10-25
supportsReasoning: true,
canTurnOffReasoning: true,
canIOReasoning: false,
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
reasoningReservedOutputTokenSpace: 8192,
},
reasoningCapabilities: false,
},
'gemini-2.0-flash-lite-preview-02-05': {
contextWindow: 1_048_576,
@ -835,7 +909,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
const geminiSettings: VoidStaticProviderInfo = {
modelOptions: geminiModelOptions,
modelOptionsFallback: (modelName) => { return null }
modelOptionsFallback: (modelName) => { return null },
}
@ -861,11 +935,12 @@ const deepseekModelOptions = {
const deepseekSettings: VoidStaticProviderInfo = {
modelOptions: deepseekModelOptions,
modelOptionsFallback: (modelName) => { return null },
providerReasoningIOSettings: {
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://api-docs.deepseek.com/guides/reasoning_model
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
output: { nameOfFieldInDelta: 'reasoning_content' },
},
modelOptionsFallback: (modelName) => { return null }
}
@ -900,6 +975,17 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'devstral-small-latest': { //https://openrouter.ai/mistralai/devstral-small:free
contextWindow: 131_000,
reservedOutputTokenSpace: 8_192,
cost: { input: 0, output: 0 },
supportsFIM: false,
downloadable: { sizeGb: 14 }, //https://ollama.com/library/devstral
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'ministral-8b-latest': { // ollama 'mistral'
contextWindow: 131_000,
reservedOutputTokenSpace: 4_096,
@ -923,6 +1009,9 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici
const mistralSettings: VoidStaticProviderInfo = {
modelOptions: mistralModelOptions,
modelOptionsFallback: (modelName) => { return null },
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
@ -966,11 +1055,13 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
},
} as const satisfies { [s: string]: VoidStaticModelInfo }
const groqSettings: VoidStaticProviderInfo = {
modelOptions: groqModelOptions,
modelOptionsFallback: (modelName) => { return null },
providerReasoningIOSettings: {
// Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning
input: {
includeInPayload: (reasoningInfo) => {
if (!reasoningInfo?.isReasoningEnabled) return null
if (reasoningInfo.type === 'budget_slider_value') {
return { reasoning_format: 'parsed' }
}
@ -978,9 +1069,7 @@ const groqSettings: VoidStaticProviderInfo = {
}
},
output: { nameOfFieldInDelta: 'reasoning' },
}, // Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning
modelOptions: groqModelOptions,
modelOptionsFallback: (modelName) => { return null }
},
}
@ -989,7 +1078,10 @@ const googleVertexModelOptions = {
} as const satisfies Record<string, VoidStaticModelInfo>
const googleVertexSettings: VoidStaticProviderInfo = {
modelOptions: googleVertexModelOptions,
modelOptionsFallback: (modelName) => { return null }
modelOptionsFallback: (modelName) => { return null },
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
// ---------------- MICROSOFT AZURE ----------------
@ -997,7 +1089,10 @@ const microsoftAzureModelOptions = {
} as const satisfies Record<string, VoidStaticModelInfo>
const microsoftAzureSettings: VoidStaticProviderInfo = {
modelOptions: microsoftAzureModelOptions,
modelOptionsFallback: (modelName) => { return null }
modelOptionsFallback: (modelName) => { return null },
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
@ -1066,42 +1161,66 @@ const ollamaModelOptions = {
supportsSystemMessage: 'system-role',
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] },
},
'devstral:latest': {
contextWindow: 131_000,
reservedOutputTokenSpace: 8_192,
cost: { input: 0, output: 0 },
downloadable: { sizeGb: 14 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
} as const satisfies Record<string, VoidStaticModelInfo>
export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[]
export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1', 'devstral:latest'] as const satisfies (keyof typeof ollamaModelOptions)[]
const vLLMSettings: VoidStaticProviderInfo = {
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://docs.vllm.ai/en/stable/features/reasoning_outputs.html#streaming-chat-completions
providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' }, },
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
modelOptions: {}, // TODO
modelOptions: {},
providerReasoningIOSettings: {
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://docs.vllm.ai/en/stable/features/reasoning_outputs.html#streaming-chat-completions
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
output: { nameOfFieldInDelta: 'reasoning_content' },
},
}
const lmStudioSettings: VoidStaticProviderInfo = {
providerReasoningIOSettings: { output: { needsManualParse: true }, },
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' }, contextWindow: 4_096 }),
modelOptions: {}, // TODO
modelOptions: {},
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
output: { needsManualParse: true },
},
}
const ollamaSettings: VoidStaticProviderInfo = {
// reasoning: we need to filter out reasoning <think> tags manually
providerReasoningIOSettings: { output: { needsManualParse: true }, },
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
modelOptions: ollamaModelOptions,
providerReasoningIOSettings: {
// reasoning: we need to filter out reasoning <think> tags manually
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
output: { needsManualParse: true },
},
}
const openaiCompatible: VoidStaticProviderInfo = {
// reasoning: we have no idea what endpoint they used, so we can't consistently parse out reasoning
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName),
modelOptions: {},
providerReasoningIOSettings: {
// reasoning: we have no idea what endpoint they used, so we can't consistently parse out reasoning
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
const liteLLMSettings: VoidStaticProviderInfo = { // https://docs.litellm.ai/docs/reasoning_content
providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' } },
modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
modelOptions: {}, // TODO
modelOptions: {},
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
output: { nameOfFieldInDelta: 'reasoning_content' },
},
}
@ -1168,6 +1287,24 @@ const openRouterModelOptions_assumingOpenAICompat = {
cost: { input: 0.8, output: 2.4 },
downloadable: false,
},
'anthropic/claude-opus-4': {
contextWindow: 200_000,
reservedOutputTokenSpace: null,
cost: { input: 15.00, output: 75.00 },
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'anthropic/claude-sonnet-4': {
contextWindow: 200_000,
reservedOutputTokenSpace: null,
cost: { input: 15.00, output: 75.00 },
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'anthropic/claude-3.7-sonnet:thinking': {
contextWindow: 200_000,
reservedOutputTokenSpace: null,
@ -1209,6 +1346,14 @@ const openRouterModelOptions_assumingOpenAICompat = {
downloadable: false,
reasoningCapabilities: false,
},
'mistralai/devstral-small:free': {
...openSourceModelOptions_assumingOAICompat.devstral,
contextWindow: 130_000,
reservedOutputTokenSpace: null,
cost: { input: 0, output: 0 },
downloadable: false,
reasoningCapabilities: false,
},
'qwen/qwen-2.5-coder-32b-instruct': {
...openSourceModelOptions_assumingOAICompat['qwen2.5coder'],
contextWindow: 33_000,
@ -1226,8 +1371,18 @@ const openRouterModelOptions_assumingOpenAICompat = {
} as const satisfies { [s: string]: VoidStaticModelInfo }
const openRouterSettings: VoidStaticProviderInfo = {
// reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models
modelOptions: openRouterModelOptions_assumingOpenAICompat,
// TODO!!! send a query to openrouter to get the price, etc.
modelOptionsFallback: (modelName) => {
const res = extensiveModelOptionsFallback(modelName)
// openRouter does not support gemini-style, use openai-style instead
if (res?.specialToolFormat === 'gemini-style') {
res.specialToolFormat = 'openai-style'
}
return res
},
providerReasoningIOSettings: {
// reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models
input: {
// https://openrouter.ai/docs/use-cases/reasoning-tokens
includeInPayload: (reasoningInfo) => {
@ -1251,16 +1406,6 @@ const openRouterSettings: VoidStaticProviderInfo = {
},
output: { nameOfFieldInDelta: 'reasoning' },
},
modelOptions: openRouterModelOptions_assumingOpenAICompat,
// TODO!!! send a query to openrouter to get the price, etc.
modelOptionsFallback: (modelName) => {
const res = extensiveModelOptionsFallback(modelName)
// openRouter does not support gemini-style, use openai-style instead
if (res?.specialToolFormat === 'gemini-style') {
res.specialToolFormat = 'openai-style'
}
return res
},
}

View file

@ -481,7 +481,6 @@ ${directoryStr}
details.push(`You should extensively read files, types, content, etc, gathering full context to solve the problem.`)
}
details.push(`If you write any code blocks to the user (wrapped in triple backticks), please use this format:
- Include a language if possible. Terminal should have the language 'shell'.
- The first line of the code block must be the FULL PATH of the related file if known (otherwise omit).
@ -529,7 +528,9 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`)
// chat_systemMessage({ chatMode, workspaceFolders: [], openedURIs: [], activeURI: 'pee', persistentTerminalIDs: [], directoryStr: 'lol', }))
// }
const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{
export const DEFAULT_FILE_SIZE_LIMIT = 2_000_000
export const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{
val: string,
truncated: boolean,
fullFileLen: number,
@ -553,46 +554,70 @@ const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: numb
export const messageOfSelection = async (
s: StagingSelectionItem,
opts: {
directoryStrService: IDirectoryStrService,
fileService: IFileService,
folderOpts: {
maxChildren: number,
maxCharsPerFile: number,
}
}
) => {
const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})`
if (s.type === 'File' || s.type === 'CodeSelection') {
const { val } = await readFile(opts.fileService, s.uri, DEFAULT_FILE_SIZE_LIMIT)
const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : ''
const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}`
const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}`
return str
}
else if (s.type === 'Folder') {
const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri)
const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}`
const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: opts.folderOpts.maxChildren })
const strOfFiles = await Promise.all(uris.map(async uri => {
const { val, truncated } = await readFile(opts.fileService, uri, opts.folderOpts.maxCharsPerFile)
const truncationStr = truncated ? `\n... file truncated ...` : ''
const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}`
const str = `${uri.fsPath}:\n${content}`
return str
}))
const contentStr = [folderStructure, ...strOfFiles].join('\n\n')
return contentStr
}
else
return ''
}
export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null,
opts: { directoryStrService: IDirectoryStrService, fileService: IFileService }
export const chat_userMessageContent = async (
instructions: string,
currSelns: StagingSelectionItem[] | null,
opts: {
directoryStrService: IDirectoryStrService,
fileService: IFileService
},
) => {
const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})`
let selnsStrs: string[] = []
selnsStrs = await Promise.all(currSelns?.map(async (s) => {
const selnsStrs = await Promise.all(
(currSelns ?? []).map(async (s) =>
messageOfSelection(s, {
...opts,
folderOpts: { maxChildren: 100, maxCharsPerFile: 100_000, }
})
)
)
if (s.type === 'File' || s.type === 'CodeSelection') {
const { val } = await readFile(opts.fileService, s.uri, 2_000_000)
const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : ''
const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}`
const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}`
return str
}
else if (s.type === 'Folder') {
const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri)
const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}`
const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: 100 })
const strOfFiles = await Promise.all(uris.map(async uri => {
const { val, truncated } = await readFile(opts.fileService, uri, 100_000)
const truncationStr = truncated ? `\n... file truncated ...` : ''
const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}`
const str = `${uri.fsPath}:\n${content}`
return str
}))
const contentStr = [folderStructure, ...strOfFiles].join('\n\n')
return contentStr
}
else
return ''
}) ?? [])
const selnsStr = selnsStrs.join('\n') ?? ''
let str = ''
str += `${instructions}`
const selnsStr = selnsStrs.join('\n\n') ?? ''
if (selnsStr) str += `\n---\nSELECTIONS\n${selnsStr}`
return str;
}

View file

@ -7,7 +7,7 @@
/* eslint-disable */
import Anthropic from '@anthropic-ai/sdk';
import { Ollama } from 'ollama';
import OpenAI, { ClientOptions } from 'openai';
import OpenAI, { ClientOptions, AzureOpenAI } from 'openai';
import { MistralCore } from '@mistralai/mistralai/core.js';
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
import { Tool as GeminiTool, FunctionDeclaration, GoogleGenAI, ThinkingConfig, Schema, Type } from '@google/genai';
@ -114,9 +114,12 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
}
else if (providerName === 'microsoftAzure') {
// https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions?view=rest-aifoundry-model-inference-2024-05-01-preview&tabs=HTTP
// https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai
const thisConfig = settingsOfProvider[providerName]
const baseURL = `https://${thisConfig.project}.services.ai.azure.com/api/models/chat/completions??api-version=${thisConfig.azureApiVersion}`
return new OpenAI({ baseURL: baseURL, apiKey: thisConfig.apiKey, ...commonPayloadOpts })
const endpoint = `https://${thisConfig.project}.openai.azure.com/`;
const apiVersion = thisConfig.azureApiVersion ?? '2024-04-01-preview';
const options = { endpoint, apiKey: thisConfig.apiKey, apiVersion };
return new AzureOpenAI({ ...options, ...commonPayloadOpts });
}
else if (providerName === 'deepseek') {
@ -147,7 +150,12 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, overridesOfModel }: SendFIMParams_Internal) => {
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_, overridesOfModel)
const {
modelName,
supportsFIM,
additionalOpenAIPayload,
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
if (!supportsFIM) {
if (modelName === modelName_)
onError({ message: `Model ${modelName} does not support FIM.`, fullError: null })
@ -156,7 +164,7 @@ const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens
return
}
const openai = await newOpenAICompatibleSDK({ providerName, settingsOfProvider })
const openai = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload: additionalOpenAIPayload })
openai.completions
.create({
model: modelName,
@ -236,6 +244,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
modelName,
specialToolFormat,
reasoningCapabilities,
additionalOpenAIPayload,
} = getModelCapabilities(providerName, modelName_, overridesOfModel)
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
@ -243,7 +252,11 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
// reasoning
const { canIOReasoning, openSourceThinkTags } = reasoningCapabilities || {}
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
const includeInPayload = {
...providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo),
...additionalOpenAIPayload
}
// tools
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
@ -253,11 +266,16 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
// instance
const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
if (providerName === 'microsoftAzure') {
// Required to select the model
(openai as AzureOpenAI).deploymentName = modelName;
}
const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelName,
messages: messages as any,
stream: true,
...nativeToolsObj,
...additionalOpenAIPayload
// max_completion_tokens: maxTokens,
}