Compare commits

...

30 Commits

Author SHA1 Message Date
Andrew Pareles 5a5732601e claude 4 2025-05-22 11:22:36 -07:00
Andrew Pareles 906502f660 Merge pull request #590 from voideditor/model-selection
Auto update message + terminal tool service improvements on Windows
2025-05-16 13:29:57 -07:00
Andrew Pareles a8bc42b524 Merge pull request #595 from zpg6/fix/azure-model-selection
fix: select AzureOpenAI deployment based on selected model
2025-05-15 21:01:32 -07:00
Zach Grimaldi d7201a38dc fix: select AzureOpenAI deployment based on selected model 2025-05-15 11:34:56 -04:00
Andrew Pareles 9112c0c94d only 1 void update notif at a time 2025-05-14 15:11:05 -07:00
Andrew Pareles 5cab7e61e5 1.3.10 2025-05-13 23:37:48 -07:00
Andrew Pareles c405a462a8 Merge pull request #584 from voideditor/model-selection
add reasoning low/med/hi for all openai-compat providers
2025-05-13 23:37:34 -07:00
Andrew Pareles e481327327 add reasoning low/med/hi for all openai-compat providers 2025-05-13 23:35:19 -07:00
Andrew Pareles b7bb7f2f94 Merge pull request #583 from voideditor/model-selection
1.3.9
2025-05-13 22:59:47 -07:00
Andrew Pareles 2ae288e2ed 1.3.9 2025-05-13 22:59:28 -07:00
Andrew Pareles 316709671f Merge pull request #582 from voideditor/model-selection
fix certain cases of advanced settings
2025-05-13 22:59:16 -07:00
Andrew Pareles 9fac1e27a9 fix certain cases of advanced settings 2025-05-13 22:58:37 -07:00
Andrew Pareles f632327a7e Merge pull request #580 from voideditor/model-selection
1.3.8
2025-05-13 21:33:26 -07:00
Andrew Pareles e95922802f rm azure identity 2025-05-13 21:32:57 -07:00
Andrew Pareles 3954056a41 1.3.8 2025-05-13 21:32:21 -07:00
Andrew Pareles 1c7acdac94 Merge remote-tracking branch 'origin/main' into model-selection 2025-05-13 21:31:39 -07:00
Andrew Pareles 33163b7e81 Merge branch 'main' into model-selection 2025-05-13 21:31:09 -07:00
Andrew Pareles d9d3147b2a Merge pull request #577 from voideditor/azure-fix
Fix Azure
2025-05-13 21:06:03 -07:00
Andrew Pareles 4b9e2c2319 azure link 2025-05-13 21:02:44 -07:00
Andrew Pareles 19bd649451 Merge pull request #579 from voideditor/model-selection
Fix Grok 3
2025-05-13 21:01:05 -07:00
Andrew Pareles 7fd06b946b fallback 2025-05-13 21:00:47 -07:00
Andrew Pareles 8c760980b6 fix grok 3 2025-05-13 21:00:20 -07:00
Andrew Pareles 96000273cf payload 2025-05-13 20:05:35 -07:00
Andrew Pareles e771edbe2e update azure 2025-05-13 20:04:40 -07:00
Andrew Pareles aafe556333 azure via AzureOpenAI 2025-05-13 20:00:39 -07:00
Andrew Pareles 9502f563f4 Merge pull request #576 from voideditor/model-selection
Refresh blank window
2025-05-13 19:56:11 -07:00
Andrew Pareles 78c6baed82 revert 2025-05-13 19:54:45 -07:00
Andrew Pareles bafbdd9df3 Merge pull request #575 from voideditor/model-selection
fix anthropic empty err message
2025-05-13 19:53:20 -07:00
Andrew Pareles 4b86bfa184 Merge pull request #562 from voideditor/model-selection
bump version
2025-05-12 20:16:26 -07:00
Andrew Pareles 7c448f15db Merge pull request #560 from voideditor/model-selection
Azure?
2025-05-12 19:50:57 -07:00
7 changed files with 240 additions and 107 deletions
+32 -14
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",
+2 -2
View File
@@ -1,8 +1,8 @@
{
"nameShort": "Void",
"nameLong": "Void",
"voidVersion": "1.3.7",
"voidRelease": "0031",
"voidVersion": "1.3.10",
"voidRelease": "0034",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",
@@ -2856,16 +2856,13 @@ export const SidebarChat = () => {
// resolve mount info
const isResolved = chatThreadsState.allThreads[threadId]?.state.mountedInfo?.mountedIsResolvedRef.current
useEffect(() => {
if (isResolved) return
chatThreadsState.allThreads[threadId]?.state.mountedInfo?._whenMountedResolver?.({
textAreaRef: textAreaRef,
scrollToBottom: () => scrollToBottom(scrollContainerRef),
})
// Trigger a window resize event to ensure proper layout calculations
window.dispatchEvent(new Event('resize'))
}, [chatThreadsState, threadId, textAreaRef, scrollContainerRef, isResolved])
@@ -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';
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
@@ -185,6 +185,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,
@@ -208,39 +213,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);
@@ -251,9 +243,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;
@@ -265,10 +258,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;
}
}
@@ -333,10 +326,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 && (
@@ -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
}
}
})
@@ -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,6 +108,8 @@ 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',
@@ -188,14 +192,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'
| 'additionalOpenAIPayload'
export const modelOverrideKeys = [
'contextWindow',
'reservedOutputTokenSpace',
'supportsSystemMessage',
'specialToolFormat',
'supportsFIM',
'reasoningCapabilities',
'additionalOpenAIPayload'
] as const
export type ModelOverrides = Pick<
VoidStaticModelInfo,
(typeof modelOverrideKeys)[number]
>
@@ -386,7 +396,8 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
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')
@@ -458,6 +469,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,
@@ -517,6 +562,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'
@@ -630,6 +679,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) => {
@@ -642,17 +701,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 },
},
}
@@ -667,6 +716,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: false,
},
'grok-3': {
@@ -676,6 +726,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: false,
},
'grok-3-fast': {
@@ -685,6 +736,7 @@ const xAIModelOptions = {
downloadable: false,
supportsFIM: false,
supportsSystemMessage: 'system-role',
specialToolFormat: 'openai-style',
reasoningCapabilities: false,
},
// only mini supports thinking
@@ -695,6 +747,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': {
@@ -704,6 +757,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 }
@@ -714,11 +768,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 },
},
}
@@ -837,7 +895,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
const geminiSettings: VoidStaticProviderInfo = {
modelOptions: geminiModelOptions,
modelOptionsFallback: (modelName) => { return null }
modelOptionsFallback: (modelName) => { return null },
}
@@ -863,11 +921,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 }
}
@@ -925,6 +984,9 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici
const mistralSettings: VoidStaticProviderInfo = {
modelOptions: mistralModelOptions,
modelOptionsFallback: (modelName) => { return null },
providerReasoningIOSettings: {
input: { includeInPayload: openAICompatIncludeInPayloadReasoning },
},
}
@@ -968,11 +1030,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' }
}
@@ -980,9 +1044,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 }
},
}
@@ -991,7 +1053,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 ----------------
@@ -999,7 +1064,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 },
},
}
@@ -1075,35 +1143,50 @@ export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq',
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' },
},
}
@@ -1170,6 +1253,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,
@@ -1228,8 +1329,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) => {
@@ -1253,16 +1364,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
},
}
@@ -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') {
@@ -263,6 +266,10 @@ 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,