From 14a276e4a0ce88251d5df1dbe1dc022fe979f595 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 1 May 2025 04:04:10 -0700 Subject: [PATCH 01/34] flatten settings --- .voidrules | 3 ++- .../contrib/void/browser/editCodeService.ts | 2 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 2 +- .../react/src/void-settings-tsx/Settings.tsx | 19 +++++++------------ 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.voidrules b/.voidrules index c06e70b4..08826ef2 100644 --- a/.voidrules +++ b/.voidrules @@ -2,4 +2,5 @@ This is a fork of the VSCode repo called Void. Most code we care about lives in src/vs/workbench/contrib/void. -You may sometimes need to explore the full repo to find relevant parts of code. +You may often need to explore the full repo to find relevant parts of code. +Look for services, and built-in functions that you might need to use to solve the problem. \ No newline at end of file diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index a106e601..f099fa25 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1563,7 +1563,7 @@ class EditCodeService extends Disposable implements IEditCodeService { const descStr = str === `Not found` ? `The edit was not applied. The text in ORIGINAL must EXACTLY match lines of code in the file, but there was no match for:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code matches a code excerpt exactly.` : str === `Not unique` ? - `The edit was not applied. The text in ORIGINAL must be unique, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.` + `The edit was not applied. The text in ORIGINAL must be unique in the file being edited, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.` : str === 'Has overlap' ? `The edit was not applied. The text in the ORIGINAL blocks must not overlap, but the following ORIGINAL code had overlap with another ORIGINAL string:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code blocks do not overlap.` : `` diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 8733e140..3d7a7348 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -722,7 +722,7 @@ const ToolHeaderWrapper = ({ return (
{/* header */} -
+
{/* left */}
{

Feature Options

- {/* L1 */} -
+
{/* FIM */} -
+

{displayInfoOfFeatureName('Autocomplete')}

-
+
Experimental.{' '} @@ -1057,7 +1056,7 @@ export const Settings = () => {

{displayInfoOfFeatureName('Apply')}

-
Settings that control the behavior of the Apply button.
+
Settings that control the behavior of the Apply button.
{/* Sync to Chat Switch */} @@ -1087,18 +1086,14 @@ export const Settings = () => {
-
- {/* L2 */} - -
{/* Tools Section */} -
+

Tools

-
{`Tools are functions that LLMs can call. Some tools require user approval.`}
+
{`Tools are functions that LLMs can call. Some tools require user approval.`}
{/* Auto Accept Switch */} @@ -1130,7 +1125,7 @@ export const Settings = () => {

Editor

-
{`Settings that control the visibility of Void suggestions in the code editor.`}
+
{`Settings that control the visibility of Void suggestions in the code editor.`}
{/* Auto Accept Switch */} From 5cd1fa15cfd74bb00da689bb7573d14c32a6d72d Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 1 May 2025 18:04:35 -0700 Subject: [PATCH 02/34] copy --- .../workbench/contrib/void/browser/directoryStrService.ts | 6 +++--- .../void/browser/react/src/void-settings-tsx/Settings.tsx | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/directoryStrService.ts b/src/vs/workbench/contrib/void/browser/directoryStrService.ts index dcdbaa42..80f70373 100644 --- a/src/vs/workbench/contrib/void/browser/directoryStrService.ts +++ b/src/vs/workbench/contrib/void/browser/directoryStrService.ts @@ -16,14 +16,14 @@ import { ExplorerItem } from '../../files/common/explorerModel.js'; import { MAX_CHILDREN_URIs_PAGE, MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from '../common/prompt/prompts.js'; -const MAX_FILES_TOTAL = 300; +const MAX_FILES_TOTAL = 1000; -const DEFAULT_MAX_DEPTH = 3; -const DEFAULT_MAX_ITEMS_PER_DIR = 3; const START_MAX_DEPTH = Infinity; const START_MAX_ITEMS_PER_DIR = Infinity; // Add start value as Infinity +const DEFAULT_MAX_DEPTH = 3; +const DEFAULT_MAX_ITEMS_PER_DIR = 3; export interface IDirectoryStrService { readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 8544d7f1..0f03e962 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -1148,7 +1148,7 @@ export const Settings = () => {

One-Click Switch

-

{`Transfer your settings from another editor to Void in one click.`}

+

{`Transfer your editor settings from another editor to Void in one click.`}

@@ -1161,6 +1161,7 @@ export const Settings = () => { {/* Import/Export section, as its own block right after One-Click Switch */}

Import/Export

+

{`Transfer your settings and chats in and out of Void.`}

{/* Settings Subcategory */}
From d3d62652dbca2605b1c6f0be84a261bfa221e9b8 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 1 May 2025 21:47:54 -0700 Subject: [PATCH 03/34] copy --- .../void/browser/react/src/void-settings-tsx/Settings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 0f03e962..6b58b1a4 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -1148,7 +1148,7 @@ export const Settings = () => {

One-Click Switch

-

{`Transfer your editor settings from another editor to Void in one click.`}

+

{`Transfer your editor settings into Void.`}

@@ -1161,7 +1161,7 @@ export const Settings = () => { {/* Import/Export section, as its own block right after One-Click Switch */}

Import/Export

-

{`Transfer your settings and chats in and out of Void.`}

+

{`Transfer Void's settings and chats in and out of Void.`}

{/* Settings Subcategory */}
From 1af393983b8b4d04395f2ef6e5d84c93afedb129 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 1 May 2025 21:52:38 -0700 Subject: [PATCH 04/34] rewrite file tool streaming fix --- .../contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 3d7a7348..1a0cab67 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -2764,7 +2764,7 @@ export const SidebarChat = () => { const { displayContentSoFar, toolCallSoFar, reasoningSoFar } = currThreadStreamState?.llmInfo ?? {} // this is just if it's currently being generated, NOT if it's currently running - const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone && toolCallSoFar.name === 'edit_file' // show loading for slow tools (right now just edit) + const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone // show loading for slow tools (right now just edit) // ----- SIDEBAR CHAT state (local) ----- From 5f12c7a1cfbd6f22850d93bb471d0c108f1ea422 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Thu, 1 May 2025 22:04:33 -0700 Subject: [PATCH 05/34] fix submit button click --- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 1a0cab67..e35193a9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -2913,11 +2913,9 @@ export const SidebarChat = () => { } }, [onSubmit, onAbort, isRunning]) - - const inputChatArea = onSubmit()} onAbort={onAbort} isStreaming={!!isRunning} isDisabled={isDisabled} From 4fb0d7d7276baaa570762ff88245a1f804d92c11 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 2 May 2025 15:56:36 -0700 Subject: [PATCH 06/34] providerName --- src/vs/workbench/contrib/void/common/voidSettingsTypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index 2971b994..7c9e854b 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -83,7 +83,7 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn return { title: 'LM Studio', } } else if (providerName === 'openAICompatible') { - return { title: 'Custom', } + return { title: 'OpenAI-Compatible', } } else if (providerName === 'gemini') { return { title: 'Gemini', } @@ -117,7 +117,7 @@ export const subTextMdOfProviderName = (providerName: ProviderName): string => { if (providerName === 'groq') return 'Get your [API Key here](https://console.groq.com/keys).' if (providerName === 'xAI') return 'Get your [API Key here](https://console.x.ai).' if (providerName === 'mistral') return 'Get your [API Key here](https://console.mistral.ai/api-keys).' - if (providerName === 'openAICompatible') return `Use any provider that's OpenAI-compatible (most popular ones are).` + if (providerName === 'openAICompatible') return `Use any provider that's OpenAI-compatible (use this for llama.cpp and more).` // if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with Void. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).' if (providerName === 'microsoftAzure') return 'Read more about endpoints [here](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), and get your API key [here](https://learn.microsoft.com/en-us/azure/search/search-security-api-keys?tabs=rest-use%2Cportal-find%2Cportal-query#find-existing-keys).' if (providerName === 'ollama') return 'If you would like to change this endpoint, please read more about [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' From 5090e31a89d67b345b8c0e9cd256587b01239308 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 3 May 2025 16:41:41 -0700 Subject: [PATCH 07/34] add model overrides and headers JSON! --- package-lock.json | 159 +++++++++- package.json | 1 + .../contrib/void/browser/chatThreadService.ts | 3 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 2 +- .../react/src/void-settings-tsx/Settings.tsx | 292 +++++++++++++++++- .../contrib/void/common/modelCapabilities.ts | 100 ++++-- .../contrib/void/common/prompt/prompts.ts | 2 +- .../void/common/voidSettingsService.ts | 43 ++- .../contrib/void/common/voidSettingsTypes.ts | 84 +++-- .../llmMessage/sendLLMMessage.impl.ts | 48 ++- .../llmMessage/sendLLMMessage.ts | 2 +- 11 files changed, 647 insertions(+), 89 deletions(-) diff --git a/package-lock.json b/package-lock.json index a365be59..7057acfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "cross-spawn": "^7.0.6", "diff": "^7.0.0", "eslint-plugin-react": "^7.37.5", + "google-auth-library": "^9.15.1", "groq-sdk": "^0.20.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -6023,6 +6024,15 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6253,6 +6263,12 @@ "node": ">=0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -8100,6 +8116,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/editorconfig": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.2.tgz", @@ -9340,8 +9365,7 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/extend-shallow": { "version": "3.0.2", @@ -10308,6 +10332,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -10944,6 +11030,32 @@ "node": ">= 0.10" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -11016,6 +11128,19 @@ "undici-types": "~5.26.4" } }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -13975,6 +14100,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -14095,6 +14229,27 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kerberos": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-2.1.1.tgz", diff --git a/package.json b/package.json index 462cf549..767d6b49 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "cross-spawn": "^7.0.6", "diff": "^7.0.0", "eslint-plugin-react": "^7.37.5", + "google-auth-library": "^9.15.1", "groq-sdk": "^0.20.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index ae443a56..7c9eb899 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -682,8 +682,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { let shouldRetryLLM = true let nAttempts = 0 while (shouldRetryLLM) { - shouldRetryLLM = false + nAttempts += 1 let resMessageIsDonePromise: (res: { type: 'llmDone', toolCall?: RawToolCallObj } | { type: 'llmError', error?: { message: string; fullError: Error | null; } } | { type: 'llmAborted' }) => void // resolves when user approves this tool use (or if tool doesn't require approval) const messageIsDonePromise = new Promise<{ type: 'llmDone', toolCall?: RawToolCallObj } | { type: 'llmError', error?: { message: string; fullError: Error | null; } } | { type: 'llmAborted' }>((res, rej) => { resMessageIsDonePromise = res }) @@ -737,7 +737,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { else if (llmRes.type === 'llmError') { // error, should retry if (nAttempts < CHAT_RETRIES) { - nAttempts += 1 shouldRetryLLM = true this._setStreamState(threadId, { isRunning: 'idle', interrupt: idleInterruptor }) await timeout(RETRY_DELAY) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index e35193a9..568c5aac 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -722,7 +722,7 @@ const ToolHeaderWrapper = ({ return (
{/* header */} -
+
{/* left */}
{ +// Import the getModelCapabilities function to access default values +import { defaultModelOptions, getModelCapabilities, ModelOverrideOptions } from '../../../../common/modelCapabilities.js'; +// Modal dialog to show model settings +const ModelSettingsDialog = ({ + isOpen, + onClose, + modelInfo +}: { + isOpen: boolean, + onClose: () => void, + modelInfo: { modelName: string, providerName: ProviderName, type: string } | null +}) => { + if (!isOpen || !modelInfo) return null; + + const { modelName, providerName } = modelInfo; + const accessor = useAccessor(); + const settingsStateService = accessor.get('IVoidSettingsService'); + const settingsState = useSettingsState(); + + // Get current model capabilities and override settings + const modelCapabilities = getModelCapabilities(providerName, modelName, settingsState.overridesOfModel); + + // Initialize form state for all potential override options + const [formValues, setFormValues] = useState<{ + contextWindow: string; + maxOutputTokens: string; + supportsTools: 'openai-style' | undefined | ''; + supportsSystemMessage: 'system-role' | 'developer-role' | false | ''; + supportsFIM: boolean | null; + reasoningCapabilities: boolean | null; + }>({ + contextWindow: '', + maxOutputTokens: '', + supportsTools: '', + supportsSystemMessage: '', + supportsFIM: null, + reasoningCapabilities: null + }); + + // When dialog opens or model changes, reset form values + useEffect(() => { + if (isOpen && modelInfo) { + + // Get current overrides + const overrides = settingsState.overridesOfModel?.[providerName]?.[modelName] || {}; + + setFormValues({ + contextWindow: (overrides.contextWindow !== undefined) ? overrides.contextWindow?.toString() : '', + maxOutputTokens: (overrides.maxOutputTokens !== undefined) ? overrides.maxOutputTokens?.toString() : '', + supportsTools: overrides.supportsTools !== undefined ? overrides.supportsTools : '', + supportsSystemMessage: overrides.supportsSystemMessage !== undefined ? overrides.supportsSystemMessage : '', + supportsFIM: overrides.supportsFIM !== undefined ? overrides.supportsFIM : null, + reasoningCapabilities: overrides.reasoningCapabilities !== undefined ? + !!overrides.reasoningCapabilities : null + }); + } + }, [isOpen, modelInfo, settingsState.overridesOfModel, providerName, modelName]); + + // Update a single field in the form + const updateField = (field: string, value: any) => { + setFormValues(prev => ({ + ...prev, + [field]: value + })); + }; + + // Handle saving settings + const handleSave = async () => { + const settings: ModelOverrideOptions = {}; + + // Only add fields to the override if they have been changed from defaults + if (formValues.contextWindow) { + const tokens = parseInt(formValues.contextWindow); + if (!isNaN(tokens)) settings.contextWindow = tokens; + } + + if (formValues.maxOutputTokens) { + const tokens = parseInt(formValues.maxOutputTokens); + if (!isNaN(tokens)) settings.maxOutputTokens = tokens; + } + + if (formValues.supportsTools !== '') { + settings.supportsTools = formValues.supportsTools as any; + } + + if (formValues.supportsSystemMessage !== '') { + settings.supportsSystemMessage = formValues.supportsSystemMessage as any; + } + + if (formValues.supportsFIM !== null) { + settings.supportsFIM = formValues.supportsFIM; + } + + if (formValues.reasoningCapabilities !== null) { + if (formValues.reasoningCapabilities) { + settings.reasoningCapabilities = { + supportsReasoning: true, + canTurnOffReasoning: true, + canIOReasoning: true + }; + } else { + settings.reasoningCapabilities = false; + } + } + + await settingsStateService.setOverridesOfModel(providerName, modelName, settings); + onClose(); + }; + + return ( +
+
e.stopPropagation()}> +
+

Override defaults for {modelName} ({displayInfoOfProviderName(providerName).title})

+ +
+ +
+ {/* Model-specific settings */} +
+ + {/* Context window */} +
+ Context window (tokens) + updateField('contextWindow', value)} + placeholder={(modelCapabilities.contextWindow || defaultModelOptions.contextWindow) + ''} + compact={true} + className="max-w-24" + /> +
+ + {/* Maximum output tokens */} +
+ Maximum output tokens + updateField('maxOutputTokens', value)} + placeholder={(modelCapabilities.maxOutputTokens || defaultModelOptions.maxOutputTokens) + ''} + compact={true} + className="max-w-24" + /> +
+ + {/* Supports Tools */} +
+ Supports tools + updateField('supportsTools', value)} + getOptionDisplayName={(opt) => { + if (opt === '') return `Default (${modelCapabilities.specialToolFormat || 'No'})`; + return opt; + }} + getOptionDropdownName={(opt) => { + if (opt === '') return `Default`; + return opt; + }} + getOptionsEqual={(a, b) => a === b} + className="max-w-32 text-xs" + /> +
+ + {/* Supports System Message */} +
+ Supports system message + updateField('supportsSystemMessage', value)} + getOptionDisplayName={(opt) => { + if (opt === '') return `Default (${modelCapabilities.supportsSystemMessage || 'No'})`; + if (opt === false) return 'No' + if (opt === true) return 'Yes' // should never happen + return opt; + }} + getOptionDropdownName={(opt) => { + if (opt === '') return `Default`; + if (opt === false) return 'No' + if (opt === true) return 'Yes' // should never happen + return opt; + }} + getOptionsEqual={(a, b) => a === b} + className="max-w-32 text-xs" + /> +
+ + {/* Supports FIM */} +
+ Supports fill-in-the-middle (autocomplete) + updateField('supportsFIM', value)} + getOptionDisplayName={(opt) => { + if (opt === null) return `Default (${modelCapabilities.supportsFIM ? 'Yes' : 'No'})`; + return opt ? 'Yes' : 'No'; + }} + getOptionDropdownName={(opt) => { + if (opt === null) return 'Default'; + return opt ? 'Yes' : 'No'; + }} + getOptionsEqual={(a, b) => a === b} + className="max-w-32 text-xs" + /> +
+ + {/* Supports Reasoning */} +
+ Supports reasoning + updateField('reasoningCapabilities', value)} + getOptionDisplayName={(opt) => { + if (opt === null) return `Default (${modelCapabilities.reasoningCapabilities ? 'Yes' : 'No'})`; + return opt ? 'Yes' : 'No'; + }} + getOptionDropdownName={(opt) => { + if (opt === null) return 'Default'; + return opt ? 'Yes' : 'No'; + }} + getOptionsEqual={(a, b) => a === b} + className="max-w-32 text-xs" + /> +
+
+
+ +
+ + Cancel + + + Save + +
+
+
+ ); +}; + +export const ModelDump = () => { const accessor = useAccessor() const settingsStateService = accessor.get('IVoidSettingsService') - const settingsState = useSettingsState() + // State to track which model's settings dialog is open + const [openSettingsModel, setOpenSettingsModel] = useState<{ + modelName: string, + providerName: ProviderName, + type: string + } | null>(null); + // a dump of all the enabled providers' models const modelDump: (VoidStatefulModelInfo & { providerName: ProviderName, providerEnabled: boolean })[] = [] for (let providerName of providerNames) { @@ -342,8 +594,10 @@ export const ModelDump = () => { const detailAboutModel = type === 'autodetected' ? - : type === 'default' ? undefined - : + : type === 'custom' ? + + : undefined + return
{ {/* {type === 'autodetected' ? '(detected locally)' : type === 'default' ? '' : '(custom model)'} */} + {/* Settings button - only for custom or locally detected models */} + {(type === 'autodetected' || type === 'custom') && ( +
+ +
+ )} + + { settingsStateService.toggleModelHidden(providerName, modelName) }} @@ -384,6 +659,13 @@ export const ModelDump = () => {
})} + + {/* Model Settings Dialog */} + setOpenSettingsModel(null)} + modelInfo={openSettingsModel} + />
} diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 79039f7b..cc2a4fce 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -3,7 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { FeatureName, ModelSelectionOptions, ProviderName } from './voidSettingsTypes.js'; +import { FeatureName, ModelSelectionOptions, OverridesOfModel, ProviderName } from './voidSettingsTypes.js'; @@ -31,6 +31,7 @@ export const defaultProviderSettings = { openAICompatible: { endpoint: '', apiKey: '', + headersJSON: '', }, gemini: { apiKey: '', @@ -50,10 +51,10 @@ export const defaultProviderSettings = { liteLLM: { // https://docs.litellm.ai/docs/providers/openai_compatible endpoint: '', }, - // googleVertex: { // google https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library - // region: 'us-west2', - // project: '', - // }, + googleVertex: { // google https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library + region: 'us-west2', + project: '', + }, microsoftAzure: { // microsoft Azure Foundry project: '', // really 'resource' apiKey: '', @@ -129,7 +130,7 @@ export const defaultModelsOfProvider = { 'ministral-8b-latest', ], openAICompatible: [], // fallback - // googleVertex: [], + googleVertex: [], microsoftAzure: [], liteLLM: [], @@ -161,7 +162,7 @@ export type VoidStaticModelInfo = { // not stateful // reasoning options if supports reasoning readonly canTurnOffReasoning: boolean; // whether or not the user can disable reasoning mode (false if the model only supports reasoning) readonly canIOReasoning: boolean; // whether or not the model actually outputs reasoning (eg o1 lets us control reasoning but not output it) - readonly reasoningMaxOutputTokens?: number; // overrides normal maxOutputTokens // <-- UNUSED (except anthropic) + readonly reasoningMaxOutputTokens?: number; // overrides normal maxOutputTokens readonly reasoningBudgetSlider?: { type: 'slider'; min: number; max: number; default: number }; // options related specifically to model output @@ -171,6 +172,26 @@ export type VoidStaticModelInfo = { // not stateful }; } + +export type ModelOverrideOptions = Partial<{ + contextWindow: number; // input tokens + maxOutputTokens: number; // output tokens, defaults to 4092 + supportsTools: 'openai-style' | undefined; + supportsSystemMessage: 'system-role' | 'developer-role' | false; + supportsFIM: boolean; + reasoningCapabilities: false | { + readonly supportsReasoning: true; + readonly canTurnOffReasoning: boolean; + readonly canIOReasoning: boolean; + readonly reasoningMaxOutputTokens?: number; + readonly openSourceThinkTags?: [string, string]; + } +}> + + + + + type ProviderReasoningIOSettings = { // include this in payload to get reasoning input?: { includeInPayload?: (reasoningState: SendableReasoningInfo) => null | { [key: string]: any }, }; @@ -189,15 +210,15 @@ type VoidStaticProviderInfo = { // doesn't change (not stateful) -const modelOptionsDefaults: VoidStaticModelInfo = { - contextWindow: 16_000, +export const defaultModelOptions = { + contextWindow: 4_096, maxOutputTokens: 4_096, cost: { input: 0, output: 0 }, downloadable: false, supportsSystemMessage: false, supportsFIM: false, reasoningCapabilities: false, -} +} as const satisfies VoidStaticModelInfo // TODO!!! double check all context sizes below // TODO!!! add openrouter common models @@ -396,7 +417,7 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = ( if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower)) return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat]) - return toFallback(modelOptionsDefaults) + return toFallback(defaultModelOptions) } @@ -485,7 +506,7 @@ const anthropicSettings: VoidStaticProviderInfo = { if (lower.includes('claude-3-opus')) fallbackName = 'claude-3-opus-20240229' if (lower.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229' if (fallbackName) return { modelName: fallbackName, ...anthropicModelOptions[fallbackName] } - return { modelName, ...modelOptionsDefaults, maxOutputTokens: 4_096 } + return { modelName, ...defaultModelOptions, maxOutputTokens: 4_096 } }, } @@ -854,12 +875,12 @@ const groqSettings: VoidStaticProviderInfo = { // ---------------- GOOGLE VERTEX ---------------- -// const googleVertexModelOptions = { -// } as const satisfies Record -// const googleVertexSettings: VoidStaticProviderInfo = { -// modelOptions: googleVertexModelOptions, -// modelOptionsFallback: (modelName) => { return null } -// } +const googleVertexModelOptions = { +} as const satisfies Record +const googleVertexSettings: VoidStaticProviderInfo = { + modelOptions: googleVertexModelOptions, + modelOptionsFallback: (modelName) => { return null } +} // ---------------- MICROSOFT AZURE ---------------- const microsoftAzureModelOptions = { @@ -872,7 +893,7 @@ const microsoftAzureSettings: VoidStaticProviderInfo = { // ---------------- VLLM, OLLAMA, OPENAICOMPAT (self-hosted / local) ---------------- const ollamaModelOptions = { - 'qwen2.5-coder:1.5b': { + 'qwen2.5-coder:7b': { contextWindow: 32_000, maxOutputTokens: null, cost: { input: 0, output: 0 }, @@ -881,6 +902,24 @@ const ollamaModelOptions = { supportsSystemMessage: 'system-role', reasoningCapabilities: false, }, + 'qwen2.5-coder:3b': { + contextWindow: 32_000, + maxOutputTokens: null, + cost: { input: 0, output: 0 }, + downloadable: { sizeGb: 1.9 }, + supportsFIM: true, + supportsSystemMessage: 'system-role', + reasoningCapabilities: false, + }, + 'qwen2.5-coder:1.5b': { + contextWindow: 32_000, + maxOutputTokens: null, + cost: { input: 0, output: 0 }, + downloadable: { sizeGb: .986 }, + supportsFIM: true, + supportsSystemMessage: 'system-role', + reasoningCapabilities: false, + }, 'llama3.1': { contextWindow: 128_000, maxOutputTokens: null, @@ -1105,7 +1144,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi liteLLM: liteLLMSettings, lmStudio: lmStudioSettings, - // googleVertex: googleVertexSettings, + googleVertex: googleVertexSettings, microsoftAzure: microsoftAzureSettings, } as const @@ -1113,22 +1152,33 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi // ---------------- exports ---------------- // returns the capabilities and the adjusted modelName if it was a fallback -export const getModelCapabilities = (providerName: ProviderName, modelName: string): VoidStaticModelInfo & { modelName: string; isUnrecognizedModel: boolean } => { +export const getModelCapabilities = ( + providerName: ProviderName, + modelName: string, + overridesOfModel?: OverridesOfModel +): VoidStaticModelInfo & { modelName: string; isUnrecognizedModel: boolean } => { const lowercaseModelName = modelName.toLowerCase() const { modelOptions, modelOptionsFallback } = modelSettingsOfProvider[providerName] + // Get any override settings for this model + const overrides = overridesOfModel?.[providerName]?.[modelName]; + // search model options object directly first for (const modelName_ in modelOptions) { const lowercaseModelName_ = modelName_.toLowerCase() - if (lowercaseModelName === lowercaseModelName_) - return { modelName, ...modelOptions[modelName], isUnrecognizedModel: false } + if (lowercaseModelName === lowercaseModelName_) { + return { ...modelOptions[modelName], ...overrides, modelName, isUnrecognizedModel: false }; + } } const result = modelOptionsFallback(modelName) - if (result) return { ...result, isUnrecognizedModel: false } - return { modelName, ...modelOptionsDefaults, isUnrecognizedModel: true } + if (result) { + return { ...result, ...overrides, modelName: result.modelName, isUnrecognizedModel: false }; + } + + return { modelName, ...defaultModelOptions, ...overrides, isUnrecognizedModel: true }; } // non-model settings diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index d0e27fcd..14544216 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -112,7 +112,7 @@ ${searchReplaceBlockTemplate} ## Guidelines: -1. You are encouraged to output multiple changes whenever possible. +1. You may output multiple search replace blocks if needed. 2. The ORIGINAL code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. Do not add or remove any whitespace or comments from the original code. diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index ad7c6a7f..462d7899 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -11,9 +11,9 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IMetricsService } from './metricsService.js'; -import { defaultProviderSettings, getModelCapabilities } from './modelCapabilities.js'; +import { defaultProviderSettings, getModelCapabilities, ModelOverrideOptions } from './modelCapabilities.js'; import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js'; -import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode } from './voidSettingsTypes.js'; +import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel } from './voidSettingsTypes.js'; // name is the name in the dropdown @@ -41,6 +41,7 @@ export type VoidSettingsState = { readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature readonly optionsOfModelSelection: OptionsOfModelSelection; + readonly overridesOfModel: OverridesOfModel; readonly globalSettings: GlobalSettings; readonly _modelOptions: ModelOption[] // computed based on the two above items @@ -61,6 +62,7 @@ export interface IVoidSettingsService { setModelSelectionOfFeature: SetModelSelectionOfFeatureFn; setOptionsOfModelSelection: SetOptionsOfModelSelection; setGlobalSetting: SetGlobalSettingFn; + setOverridesOfModel(providerName: ProviderName, modelName: string, overrides: ModelOverrideOptions): Promise; dangerousSetState(newState: VoidSettingsState): Promise; resetState(): Promise; @@ -182,6 +184,7 @@ const _validatedModelState = (state: Omit): ...state, settingsOfProvider: newSettingsOfProvider, modelSelectionOfFeature: newModelSelectionOfFeature, + overridesOfModel: state.overridesOfModel, _modelOptions: newModelOptions, } satisfies VoidSettingsState @@ -198,6 +201,7 @@ const defaultState = () => { modelSelectionOfFeature: { 'Chat': null, 'Ctrl+K': null, 'Autocomplete': null, 'Apply': null }, globalSettings: deepClone(defaultGlobalSettings), optionsOfModelSelection: { 'Chat': {}, 'Ctrl+K': {}, 'Autocomplete': {}, 'Apply': {} }, + overridesOfModel: deepClone(defaultOverridesOfModel), _modelOptions: [], // computed later } return d @@ -267,9 +271,11 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { // the stored data structure might be outdated, so we need to update it here try { readS = { + ...defaultState(), ...readS, - ...defaultSettingsOfProvider, - ...readS.settingsOfProvider, + // no idea why this was here, seems like a bug + // ...defaultSettingsOfProvider, + // ...readS.settingsOfProvider, } for (const providerName of providerNames) { @@ -314,7 +320,8 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { return defaultState() const stateStr = await this._encryptionService.decrypt(encryptedState) - return JSON.parse(stateStr) + const state = JSON.parse(stateStr) + return state } @@ -339,12 +346,14 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { } const newGlobalSettings = this.state.globalSettings + const newOverridesOfModel = this.state.overridesOfModel const newState = { modelSelectionOfFeature: newModelSelectionOfFeature, optionsOfModelSelection: newOptionsOfModelSelection, settingsOfProvider: newSettingsOfProvider, globalSettings: newGlobalSettings, + overridesOfModel: newOverridesOfModel, } this.state = _validatedModelState(newState) @@ -422,6 +431,30 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { this._onDidChangeState.fire() } + setOverridesOfModel = async (providerName: ProviderName, modelName: string, overrides: ModelOverrideOptions) => { + const currentProviderSettings = this.state.overridesOfModel[providerName] || {}; + + const newState: VoidSettingsState = { + ...this.state, + overridesOfModel: { + ...this.state.overridesOfModel, + [providerName]: { + ...currentProviderSettings, + [modelName]: { + ...currentProviderSettings[modelName], + ...overrides + } + } + } + }; + + this.state = _validatedModelState(newState); + await this._storeState(); + this._onDidChangeState.fire(); + + this._metricsService.capture('Update Model Settings', { providerName, modelName, overrides }); + } + diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index 7c9e854b..43fc9a24 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { defaultModelsOfProvider, defaultProviderSettings } from './modelCapabilities.js'; +import { defaultModelsOfProvider, defaultProviderSettings, ModelOverrideOptions } from './modelCapabilities.js'; import { ToolApprovalType } from './toolsServiceTypes.js'; import { VoidSettingsState } from './voidSettingsService.js' @@ -97,9 +97,9 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn else if (providerName === 'mistral') { return { title: 'Mistral', } } - // else if (providerName === 'googleVertex') { - // return { title: 'Google Vertex AI', } - // } + else if (providerName === 'googleVertex') { + return { title: 'Google Vertex AI', } + } else if (providerName === 'microsoftAzure') { return { title: 'Microsoft Azure OpenAI', } } @@ -118,7 +118,7 @@ export const subTextMdOfProviderName = (providerName: ProviderName): string => { if (providerName === 'xAI') return 'Get your [API Key here](https://console.x.ai).' if (providerName === 'mistral') return 'Get your [API Key here](https://console.mistral.ai/api-keys).' if (providerName === 'openAICompatible') return `Use any provider that's OpenAI-compatible (use this for llama.cpp and more).` - // if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with Void. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).' + if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with Void. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).' if (providerName === 'microsoftAzure') return 'Read more about endpoints [here](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), and get your API key [here](https://learn.microsoft.com/en-us/azure/search/search-security-api-keys?tabs=rest-use%2Cportal-find%2Cportal-query#find-existing-keys).' if (providerName === 'ollama') return 'If you would like to change this endpoint, please read more about [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' if (providerName === 'vLLM') return 'If you would like to change this endpoint, please read more about [Endpoints here](https://docs.vllm.ai/en/latest/getting_started/quickstart.html#openai-compatible-server).' @@ -149,9 +149,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName providerName === 'openAICompatible' ? 'sk-key...' : providerName === 'xAI' ? 'xai-key...' : providerName === 'mistral' ? 'api-key...' : - // providerName === 'googleVertex' ? 'AIzaSy...' : - providerName === 'microsoftAzure' ? 'key-...' : - '', + providerName === 'googleVertex' ? 'AIzaSy...' : + providerName === 'microsoftAzure' ? 'key-...' : + '', isPasswordField: true, } @@ -162,10 +162,10 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName providerName === 'vLLM' ? 'Endpoint' : providerName === 'lmStudio' ? 'Endpoint' : providerName === 'openAICompatible' ? 'baseURL' : // (do not include /chat/completions) - // providerName === 'googleVertex' ? 'baseURL' : - providerName === 'microsoftAzure' ? 'baseURL' : - providerName === 'liteLLM' ? 'baseURL' : - '(never)', + providerName === 'googleVertex' ? 'baseURL' : + providerName === 'microsoftAzure' ? 'baseURL' : + providerName === 'liteLLM' ? 'baseURL' : + '(never)', placeholder: providerName === 'ollama' ? defaultProviderSettings.ollama.endpoint : providerName === 'vLLM' ? defaultProviderSettings.vLLM.endpoint @@ -177,14 +177,17 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName } } - // else if (settingName === 'region') { - // // vertex only - // return { - // title: 'Region', - // placeholder: providerName === 'googleVertex' ? defaultProviderSettings.googleVertex.region - // : '' - // } - // } + else if (settingName === 'headersJSON') { + return { title: 'Custom Headers', placeholder: '{ "X-Request-Id": "..." }' } + } + else if (settingName === 'region') { + // vertex only + return { + title: 'Region', + placeholder: providerName === 'googleVertex' ? defaultProviderSettings.googleVertex.region + : '' + } + } else if (settingName === 'azureApiVersion') { // azure only return { @@ -196,11 +199,11 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName else if (settingName === 'project') { return { title: providerName === 'microsoftAzure' ? 'Resource' - // : providerName === 'googleVertex' ? 'Project' - : '', + : providerName === 'googleVertex' ? 'Project' + : '', placeholder: providerName === 'microsoftAzure' ? 'my-resource' - // : providerName === 'googleVertex' ? 'my-project' - : '' + : providerName === 'googleVertex' ? 'my-project' + : '' } @@ -228,9 +231,10 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName const defaultCustomSettings: Record = { apiKey: undefined, endpoint: undefined, - // region: undefined, // googleVertex + region: undefined, // googleVertex project: undefined, azureApiVersion: undefined, + headersJSON: undefined, } @@ -324,12 +328,12 @@ export const defaultSettingsOfProvider: SettingsOfProvider = { ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.vLLM), _didFillInProviderSettings: undefined, }, - // googleVertex: { // aggregator (serves models from multiple providers) - // ...defaultCustomSettings, - // ...defaultProviderSettings.googleVertex, - // ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.googleVertex), - // _didFillInProviderSettings: undefined, - // }, + googleVertex: { // aggregator (serves models from multiple providers) + ...defaultCustomSettings, + ...defaultProviderSettings.googleVertex, + ...modelInfoOfDefaultModelNames(defaultModelsOfProvider.googleVertex), + _didFillInProviderSettings: undefined, + }, microsoftAzure: { // aggregator (serves models from multiple providers) ...defaultCustomSettings, ...defaultProviderSettings.microsoftAzure, @@ -467,8 +471,22 @@ export type ModelSelectionOptions = { export type OptionsOfModelSelection = { [featureName in FeatureName]: Partial<{ [providerName in ProviderName]: { - [modelName: string]: - ModelSelectionOptions | undefined + [modelName: string]: ModelSelectionOptions | undefined } }> } + + + + + +export type OverridesOfModel = { + [providerName in ProviderName]: { + [modelName: string]: ModelOverrideOptions | undefined + } +} + + +const overridesOfModel = {} as OverridesOfModel +for (const providerName of providerNames) { overridesOfModel[providerName] = {} } +export const defaultOverridesOfModel = overridesOfModel diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 6eb0ad7a..95b68e45 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -11,7 +11,7 @@ import OpenAI, { ClientOptions } from 'openai'; import { MistralCore } from '@mistralai/mistralai/core.js'; import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js'; import { GoogleGenerativeAI, Tool as GeminiTool, SchemaType, FunctionDeclaration, FunctionDeclarationSchemaProperty } from '@google/generative-ai'; -// import { GoogleAuth } from 'google-auth-library' +import { GoogleAuth } from 'google-auth-library' /* eslint-enable */ import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'; @@ -21,6 +21,14 @@ import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGramma import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; +const getGoogleApiKey = async () => { + // module‑level singleton + const auth = new GoogleAuth({ scopes: `https://www.googleapis.com/auth/cloud-platform` }); + const key = await auth.getAccessToken() + if (!key) throw new Error(`Google API failed to generate a key.`) + return key +} + @@ -50,6 +58,15 @@ const invalidApiKeyMessage = (providerName: ProviderName) => `Invalid ${displayI +const parseHeadersJSON = (s: string | undefined): Record | undefined => { + if (!s) return undefined + try { + return JSON.parse(s) + } catch (e) { + throw new Error(`Error parsing OpenAI-Compatible headers: ${s} is not a valid JSON.`) + } +} + const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includeInPayload }: { settingsOfProvider: SettingsOfProvider, providerName: ProviderName, includeInPayload?: { [s: string]: any } }) => { const commonPayloadOpts: ClientOptions = { dangerouslyAllowBrowser: true, @@ -87,12 +104,13 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ ...commonPayloadOpts, }) } - // else if (providerName === 'googleVertex') { - // // https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library - // const thisConfig = settingsOfProvider[providerName] - // const baseURL = `https://${thisConfig.region}-aiplatform.googleapis.com/v1/projects/${thisConfig.project}/locations/${thisConfig.region}/endpoints/${'openapi'}` - // return new OpenAI({ baseURL: baseURL, apiKey: apiKey, ...commonPayloadOpts }) - // } + else if (providerName === 'googleVertex') { + // https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library + const thisConfig = settingsOfProvider[providerName] + const baseURL = `https://${thisConfig.region}-aiplatform.googleapis.com/v1/projects/${thisConfig.project}/locations/${thisConfig.region}/endpoints/${'openapi'}` + const apiKey = await getGoogleApiKey() + return new OpenAI({ baseURL: baseURL, apiKey: apiKey, ...commonPayloadOpts }) + } 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 const thisConfig = settingsOfProvider[providerName] @@ -106,7 +124,8 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ } else if (providerName === 'openAICompatible') { const thisConfig = settingsOfProvider[providerName] - return new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, ...commonPayloadOpts }) + const headers = parseHeadersJSON(thisConfig.headersJSON) + return new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, defaultHeaders: headers, ...commonPayloadOpts }) } else if (providerName === 'groq') { const thisConfig = settingsOfProvider[providerName] @@ -843,20 +862,21 @@ export const sendLLMMessageToProviderImplementation = { }, lmStudio: { + // lmStudio has no suffix parameter in /completions, so sendFIM might not work sendChat: (params) => _sendOpenAICompatibleChat(params), - sendFIM: null, // lmStudio has no suffix parameter in /completions + sendFIM: (params) => _sendOpenAICompatibleFIM(params), list: (params) => _openaiCompatibleList(params), }, liteLLM: { + sendChat: (params) => _sendOpenAICompatibleChat(params), + sendFIM: (params) => _sendOpenAICompatibleFIM(params), + list: null, + }, + googleVertex: { sendChat: (params) => _sendOpenAICompatibleChat(params), sendFIM: null, list: null, }, - // googleVertex: { - // sendChat: (params) => _sendOpenAICompatibleChat(params), - // sendFIM: null, - // list: null, - // }, microsoftAzure: { sendChat: (params) => _sendOpenAICompatibleChat(params), sendFIM: null, diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts index e1c18f50..7c92d9da 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts @@ -114,7 +114,7 @@ export const sendLLMMessage = async ({ await sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage }) return } - onError({ message: `Error: This provider does not support Autocomplete yet.`, fullError: null }) + onError({ message: `Error running Autocomplete with ${providerName} - ${modelName}.`, fullError: null }) return } onError({ message: `Error: Message type "${messagesType}" not recognized.`, fullError: null }) From 4d041411395476053fdf55f6f395ef83060d2196 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sat, 3 May 2025 17:09:43 -0700 Subject: [PATCH 08/34] fix backspace --- src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 6381867b..d91eee88 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -754,7 +754,7 @@ export const VoidInputBox2 = forwardRef(fun } if (e.key === 'Backspace') { // TODO allow user to undo this. - if (!e.currentTarget.value) { // if there is no text, remove a selection + if (!e.currentTarget.value || (e.currentTarget.selectionStart === 0 && e.currentTarget.selectionEnd === 0)) { // if there is no text or cursor is at position 0, remove a selection if (e.metaKey || e.ctrlKey) { // Ctrl+Backspace = remove all chatThreadService.popStagingSelections(Number.MAX_SAFE_INTEGER) } else { // Backspace = pop 1 selection From d841468f3b79b8496952b344c6e5de528608c69a Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sat, 3 May 2025 18:47:01 -0700 Subject: [PATCH 09/34] diffidx updates properly --- .../void/browser/voidCommandBarService.ts | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts index 6cfcb1d0..e26bf5c9 100644 --- a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts +++ b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts @@ -164,10 +164,14 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar const newSortedDiffIds = this._computeSortedDiffs(newSortedDiffZoneIds) const isStreaming = this._isAnyDiffZoneStreaming(currentDiffZones) + // When diffZones are added/removed, reset the diffIdx to 0 if we have diffs + const newDiffIdx = newSortedDiffIds.length > 0 ? 0 : null; + this._setState(uri, { sortedDiffZoneIds: newSortedDiffZoneIds, sortedDiffIds: newSortedDiffIds, - isStreaming: isStreaming + isStreaming: isStreaming, + diffIdx: newDiffIdx }) this._onDidChangeState.fire({ uri }) } @@ -182,9 +186,24 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar const currState = this.stateOfURI[uri.fsPath] if (!currState) continue // should never happen const { sortedDiffZoneIds } = currState + const oldSortedDiffIds = currState.sortedDiffIds; const newSortedDiffIds = this._computeSortedDiffs(sortedDiffZoneIds) + + // Handle diffIdx adjustment when diffs change + let newDiffIdx = currState.diffIdx; + + // Check if diffs were removed + if (oldSortedDiffIds.length > newSortedDiffIds.length && currState.diffIdx !== null) { + // If currently selected diff was removed or we have fewer diffs than the current index + if (currState.diffIdx >= newSortedDiffIds.length) { + // Select the last diff if available, otherwise null + newDiffIdx = newSortedDiffIds.length > 0 ? newSortedDiffIds.length - 1 : null; + } + } + this._setState(uri, { sortedDiffIds: newSortedDiffIds, + diffIdx: newDiffIdx // sortedDiffZoneIds, // no change // isStreaming, // no change }) @@ -298,9 +317,9 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar } // make sure diffIdx is always correct - if (newState.diffIdx && newState.diffIdx > newState.sortedDiffIds.length) { + if (newState.diffIdx !== null && newState.diffIdx > newState.sortedDiffIds.length) { newState.diffIdx = newState.sortedDiffIds.length - if (newState.diffIdx < 0) newState.diffIdx = null + if (newState.diffIdx <= 0) newState.diffIdx = null } this.stateOfURI = { @@ -437,5 +456,3 @@ class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget { super.dispose(); } } - - From d8260524441c5a1090303018a3c3d40d681c559a Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 4 May 2025 19:32:28 -0700 Subject: [PATCH 10/34] add model overrides --- .../void/browser/autocompleteService.ts | 2 + .../contrib/void/browser/chatThreadService.ts | 2 + .../browser/convertToLLMMessageService.ts | 18 +- .../contrib/void/browser/editCodeService.ts | 108 ++++-- .../react/src/sidebar-tsx/SidebarChat.tsx | 6 +- .../src/void-settings-tsx/ModelDropdown.tsx | 2 +- .../react/src/void-settings-tsx/Settings.tsx | 343 +++++++++++++----- .../contrib/void/common/modelCapabilities.ts | 37 +- .../void/common/sendLLMMessageTypes.ts | 4 +- .../void/common/voidSettingsService.ts | 13 +- .../llmMessage/sendLLMMessage.impl.ts | 30 +- .../llmMessage/sendLLMMessage.ts | 5 +- 12 files changed, 388 insertions(+), 182 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/autocompleteService.ts b/src/vs/workbench/contrib/void/browser/autocompleteService.ts index bfd62321..22c86eb6 100644 --- a/src/vs/workbench/contrib/void/browser/autocompleteService.ts +++ b/src/vs/workbench/contrib/void/browser/autocompleteService.ts @@ -790,6 +790,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ console.log('starting autocomplete...', predictionType) const featureName: FeatureName = 'Autocomplete' + const overridesOfModel = this._settingsService.state.overridesOfModel const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName] const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined @@ -807,6 +808,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ }), modelSelection, modelSelectionOptions, + overridesOfModel, logging: { loggingName: 'Autocomplete' }, onText: () => { }, // unused in FIMMessage // onText: async ({ fullText, newText }) => { diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 7c9eb899..82dfc7a3 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -641,6 +641,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { // above just defines helpers, below starts the actual function const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here + const { overridesOfModel } = this._settingsService.state let nMessagesSent = 0 let shouldSendAnotherMessage = true @@ -694,6 +695,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { messages: messages, modelSelection, modelSelectionOptions, + overridesOfModel, logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } }, separateSystemMessage: separateSystemMessage, onText: ({ fullText, fullReasoning, toolCall }) => { diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index 43b91c84..2a57ea7b 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -607,20 +607,23 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess prepareLLMSimpleMessages: IConvertToLLMMessageService['prepareLLMSimpleMessages'] = ({ simpleMessages, systemMessage, modelSelection, featureName }) => { if (modelSelection === null) return { messages: [], separateSystemMessage: undefined } + + const { overridesOfModel } = this.voidSettingsService.state + const { providerName, modelName } = modelSelection const { specialToolFormat, contextWindow, supportsSystemMessage, - } = getModelCapabilities(providerName, modelName) + } = getModelCapabilities(providerName, modelName, overridesOfModel) const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] // Get combined AI instructions const aiInstructions = this._getCombinedAIInstructions(); - const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions) - const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled }) + const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) + const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled, overridesOfModel }) const { messages, separateSystemMessage } = prepareMessages({ messages: simpleMessages, @@ -637,12 +640,15 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess } prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection }) => { if (modelSelection === null) return { messages: [], separateSystemMessage: undefined } + + const { overridesOfModel } = this.voidSettingsService.state + const { providerName, modelName } = modelSelection const { specialToolFormat, contextWindow, supportsSystemMessage, - } = getModelCapabilities(providerName, modelName) + } = getModelCapabilities(providerName, modelName, overridesOfModel) const systemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat) const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName] @@ -650,8 +656,8 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess // Get combined AI instructions const aiInstructions = this._getCombinedAIInstructions(); - const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions) - const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled }) + const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions, overridesOfModel) + const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled, overridesOfModel }) const llmMessages = this._chatMessagesToSimpleMessages(chatMessages) const { messages, separateSystemMessage } = prepareMessages({ diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index f099fa25..41f45dce 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -106,37 +106,42 @@ const removeWhitespaceExceptNewlines = (str: string): string => { // finds block.orig in fileContents and return its range in file // startingAtLine is 1-indexed and inclusive -const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, opts: { startingAtLine?: number, returnType: 'lines' | 'indices' }) => { +// returns 1-indexed lines +const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, opts: { startingAtLine?: number, returnType: 'lines' }) => { - const startLineIdx = (fileContents: string) => opts?.startingAtLine !== undefined ? + const returnAns = (fileContents: string, idx: number) => { + const startLine = numLinesOfStr(fileContents.substring(0, idx + 1)) + const numLines = numLinesOfStr(text) + const endLine = startLine + numLines - 1 + + return [startLine, endLine] as const + } + + const startingAtLineIdx = (fileContents: string) => opts?.startingAtLine !== undefined ? fileContents.split('\n').slice(0, opts.startingAtLine).join('\n').length // num characters in all lines before startingAtLine : 0 // idx = starting index in fileContents - let idx = fileContents.indexOf(text, startLineIdx(fileContents)) + let idx = fileContents.indexOf(text, startingAtLineIdx(fileContents)) + + // if idx was found + if (idx !== -1) { + return returnAns(fileContents, idx) + } + + if (!canFallbackToRemoveWhitespace) + return 'Not found' as const // try to find it ignoring all whitespace this time - if (idx === -1 && canFallbackToRemoveWhitespace) { - text = removeWhitespaceExceptNewlines(text) - fileContents = removeWhitespaceExceptNewlines(fileContents) - idx = fileContents.indexOf(text, startLineIdx(fileContents)); - } + text = removeWhitespaceExceptNewlines(text) + fileContents = removeWhitespaceExceptNewlines(fileContents) + idx = fileContents.indexOf(text, startingAtLineIdx(fileContents)); if (idx === -1) return 'Not found' as const const lastIdx = fileContents.lastIndexOf(text) if (lastIdx !== idx) return 'Not unique' as const - if (opts.returnType === 'lines') { - const startLine = fileContents.substring(0, idx).split('\n').length - const numLines = numLinesOfStr(text) - const endLine = startLine + numLines - 1 - return [startLine, endLine] as const - } - - else if (opts.returnType === 'indices') { - return [idx, idx + text.length] as const - } - else throw new Error(`findTextInCode: Invalid returnType ${opts.returnType}`) + return returnAns(fileContents, idx) } @@ -1331,6 +1336,7 @@ class EditCodeService extends Disposable implements IEditCodeService { const { from, } = opts const featureName: FeatureName = opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K' + const overridesOfModel = this._settingsService.state.overridesOfModel const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName] const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined @@ -1482,6 +1488,7 @@ class EditCodeService extends Disposable implements IEditCodeService { messages, modelSelection, modelSelectionOptions, + overridesOfModel, separateSystemMessage, chatMode: null, // not chat onText: (params) => { @@ -1556,17 +1563,30 @@ class EditCodeService extends Disposable implements IEditCodeService { } - private _errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => { - + /** + * Generates a human-readable error message for an invalid ORIGINAL search block. + */ + private _errContentOfInvalidStr = ( + str: 'Not found' | 'Not unique' | 'Has overlap', + blockOrig: string, + ): string => { const problematicCode = `${tripleTick[0]}\n${JSON.stringify(blockOrig)}\n${tripleTick[1]}` - const descStr = str === `Not found` ? - `The edit was not applied. The text in ORIGINAL must EXACTLY match lines of code in the file, but there was no match for:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code matches a code excerpt exactly.` - : str === `Not unique` ? - `The edit was not applied. The text in ORIGINAL must be unique in the file being edited, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.` - : str === 'Has overlap' ? - `The edit was not applied. The text in the ORIGINAL blocks must not overlap, but the following ORIGINAL code had overlap with another ORIGINAL string:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code blocks do not overlap.` - : `` + // use a switch for better readability / exhaustiveness check + let descStr: string + switch (str) { + case 'Not found': + descStr = `The edit was not applied. The text in ORIGINAL must EXACTLY match lines of code in the file, but there was no match for:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code matches a code excerpt exactly.` + break + case 'Not unique': + descStr = `The edit was not applied. The text in ORIGINAL must be unique in the file being edited, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.` + break + case 'Has overlap': + descStr = `The edit was not applied. The text in the ORIGINAL blocks must not overlap, but the following ORIGINAL code had overlap with another ORIGINAL string:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code blocks do not overlap.` + break + default: + descStr = '' + } return descStr } @@ -1578,22 +1598,34 @@ class EditCodeService extends Disposable implements IEditCodeService { const { model } = this._voidModelService.getModel(uri) if (!model) throw new Error(`Error applying Search/Replace blocks: File does not exist.`) const modelStr = model.getValue(EndOfLinePreference.LF) + // .split('\n').map(l => '\t' + l).join('\n') // for testing purposes only, remember to remove this + const modelStrLines = modelStr.split('\n') + + const replacements: { origStart: number; origEnd: number; block: ExtractedSearchReplaceBlock }[] = [] for (const b of blocks) { - const res = findTextInCode(b.orig, modelStr, true, { returnType: 'indices' }) + const res = findTextInCode(b.orig, modelStr, true, { returnType: 'lines' }) if (typeof res === 'string') throw new Error(this._errContentOfInvalidStr(res, b.orig)) - const [i, _] = res + let [startLine, endLine] = res + startLine -= 1 // 0-index + endLine -= 1 - replacements.push({ - origStart: i, - origEnd: i + b.orig.length - 1, // INCLUSIVE - block: b, - }) + // including newline before start + const contentBeforeStart = startLine !== 0 ? + modelStrLines.slice(0, startLine).join('\n') + '\n' + : '' + + // including endline at end + const contentUpToEnd = modelStrLines.slice(0, endLine + 1).join('\n') + + const origStart = contentBeforeStart.length; + const origEnd = contentUpToEnd.length; + + replacements.push({ origStart, origEnd, block: b }); } - // sort in increasing order replacements.sort((a, b) => a.origStart - b.origStart) @@ -1610,17 +1642,18 @@ class EditCodeService extends Disposable implements IEditCodeService { const { origStart, origEnd, block } = replacements[i] newCode = newCode.slice(0, origStart) + block.final + newCode.slice(origEnd + 1, Infinity) } + console.log('REPLACEMENTS', replacements, newCode) this._writeURIText(uri, newCode, 'wholeFileRange', { shouldRealignDiffAreas: true } ) - } private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise] | undefined { const { from, applyStr, } = opts const featureName: FeatureName = 'Apply' + const overridesOfModel = this._settingsService.state.overridesOfModel const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName] const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined @@ -1900,6 +1933,7 @@ class EditCodeService extends Disposable implements IEditCodeService { messages, modelSelection, modelSelectionOptions, + overridesOfModel, separateSystemMessage, chatMode: null, // not chat onText: (params) => { diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 568c5aac..a202973f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -153,14 +153,16 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => const voidSettingsState = useSettingsState() const modelSelection = voidSettingsState.modelSelectionOfFeature[featureName] + const overridesOfModel = voidSettingsState.overridesOfModel + if (!modelSelection) return null const { modelName, providerName } = modelSelection - const { reasoningCapabilities } = getModelCapabilities(providerName, modelName) + const { reasoningCapabilities } = getModelCapabilities(providerName, modelName, overridesOfModel) const { canTurnOffReasoning, reasoningBudgetSlider } = reasoningCapabilities || {} const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[modelName] - const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions) + const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now) return null // unused right now // return
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx index 87ddf7d6..3facd801 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx @@ -56,7 +56,7 @@ const MemoizedModelDropdown = ({ featureName, className }: { featureName: Featur useEffect(() => { const oldOptions = oldOptionsRef.current - const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode })) + const newOptions = settingsState._modelOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode, overridesOfModel: settingsState.overridesOfModel })) if (!optionsEqual(oldOptions, newOptions)) { setMemoizedOptions(newOptions) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 8c9c1461..de93ce27 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -303,7 +303,7 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam // Import the getModelCapabilities function to access default values -import { defaultModelOptions, getModelCapabilities, ModelOverrideOptions } from '../../../../common/modelCapabilities.js'; +import { getModelCapabilities, ModelOverrideOptions } from '../../../../common/modelCapabilities.js'; // Modal dialog to show model settings const ModelSettingsDialog = ({ @@ -324,45 +324,68 @@ const ModelSettingsDialog = ({ // Get current model capabilities and override settings const modelCapabilities = getModelCapabilities(providerName, modelName, settingsState.overridesOfModel); + const defaultModelCapabilities = getModelCapabilities(providerName, modelName, undefined) // Initialize form state for all potential override options const [formValues, setFormValues] = useState<{ contextWindow: string; maxOutputTokens: string; - supportsTools: 'openai-style' | undefined | ''; - supportsSystemMessage: 'system-role' | 'developer-role' | false | ''; + specialToolFormat: 'openai-style' | 'gemini-style' | 'anthropic-style' | undefined | ''; + supportsSystemMessage: 'system-role' | 'developer-role' | 'separated' | false | ''; supportsFIM: boolean | null; reasoningCapabilities: boolean | null; + canTurnOffReasoning: boolean; + reasoningMaxOutputTokens: string; + openSourceThinkTags: [string, string] | null; }>({ + // start form as default values contextWindow: '', maxOutputTokens: '', - supportsTools: '', + specialToolFormat: '', supportsSystemMessage: '', supportsFIM: null, - reasoningCapabilities: null + reasoningCapabilities: null, + canTurnOffReasoning: false, + reasoningMaxOutputTokens: '', + openSourceThinkTags: null, }); // When dialog opens or model changes, reset form values useEffect(() => { if (isOpen && modelInfo) { - // Get current overrides const overrides = settingsState.overridesOfModel?.[providerName]?.[modelName] || {}; + // Extract reasoning capabilities if available (use any to avoid TS union narrowing issues) + const reasoningCapabilities: any = typeof overrides.reasoningCapabilities === 'object' ? + overrides.reasoningCapabilities : overrides.reasoningCapabilities ? { supportsReasoning: true, canIOReasoning: true } : false; + + // Extract the think tags if they exist + let thinkTags: [string, string] | null = null; + if (typeof reasoningCapabilities === 'object' && reasoningCapabilities.openSourceThinkTags) { + thinkTags = reasoningCapabilities.openSourceThinkTags as [string, string]; + } + + // Only set values that are explicitly overridden, otherwise leave them empty + // to indicate default values should be used setFormValues({ - contextWindow: (overrides.contextWindow !== undefined) ? overrides.contextWindow?.toString() : '', - maxOutputTokens: (overrides.maxOutputTokens !== undefined) ? overrides.maxOutputTokens?.toString() : '', - supportsTools: overrides.supportsTools !== undefined ? overrides.supportsTools : '', + contextWindow: overrides.contextWindow !== undefined ? String(overrides.contextWindow) : '', + maxOutputTokens: overrides.maxOutputTokens !== undefined ? String(overrides.maxOutputTokens) : '', + specialToolFormat: overrides.specialToolFormat !== undefined ? overrides.specialToolFormat : '', supportsSystemMessage: overrides.supportsSystemMessage !== undefined ? overrides.supportsSystemMessage : '', supportsFIM: overrides.supportsFIM !== undefined ? overrides.supportsFIM : null, reasoningCapabilities: overrides.reasoningCapabilities !== undefined ? - !!overrides.reasoningCapabilities : null + !!overrides.reasoningCapabilities : null, + canTurnOffReasoning: typeof reasoningCapabilities === 'object' ? !!reasoningCapabilities.canTurnOffReasoning : false, + reasoningMaxOutputTokens: typeof reasoningCapabilities === 'object' && reasoningCapabilities.reasoningMaxOutputTokens ? + String(reasoningCapabilities.reasoningMaxOutputTokens) : '', + openSourceThinkTags: thinkTags, }); } }, [isOpen, modelInfo, settingsState.overridesOfModel, providerName, modelName]); // Update a single field in the form - const updateField = (field: string, value: any) => { + const updateField = (field: keyof typeof formValues, value: any) => { setFormValues(prev => ({ ...prev, [field]: value @@ -371,52 +394,78 @@ const ModelSettingsDialog = ({ // Handle saving settings const handleSave = async () => { - const settings: ModelOverrideOptions = {}; + // Get current overrides to see what needs to be updated/removed + const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] || {}; + const newSettings: ModelOverrideOptions = {}; - // Only add fields to the override if they have been changed from defaults - if (formValues.contextWindow) { + // Handle numeric fields - empty strings should remove the override + if (formValues.contextWindow.trim() === '') { + newSettings.contextWindow = defaultModelCapabilities.contextWindow; + } else if (formValues.contextWindow) { const tokens = parseInt(formValues.contextWindow); - if (!isNaN(tokens)) settings.contextWindow = tokens; + if (!isNaN(tokens)) newSettings.contextWindow = tokens; } - if (formValues.maxOutputTokens) { + if (formValues.maxOutputTokens.trim() === '') { + newSettings.maxOutputTokens = defaultModelCapabilities.maxOutputTokens; + } else if (formValues.maxOutputTokens) { const tokens = parseInt(formValues.maxOutputTokens); - if (!isNaN(tokens)) settings.maxOutputTokens = tokens; + if (!isNaN(tokens)) newSettings.maxOutputTokens = tokens; } - if (formValues.supportsTools !== '') { - settings.supportsTools = formValues.supportsTools as any; + // Handle dropdown fields + if (formValues.specialToolFormat === '') { + newSettings.specialToolFormat = defaultModelCapabilities.specialToolFormat + } else { + newSettings.specialToolFormat = formValues.specialToolFormat } - if (formValues.supportsSystemMessage !== '') { - settings.supportsSystemMessage = formValues.supportsSystemMessage as any; + if (formValues.supportsSystemMessage === '') { + newSettings.supportsSystemMessage = defaultModelCapabilities.supportsSystemMessage; + } else { + newSettings.supportsSystemMessage = formValues.supportsSystemMessage as any; } - if (formValues.supportsFIM !== null) { - settings.supportsFIM = formValues.supportsFIM; + if (formValues.supportsFIM === null) { + newSettings.supportsFIM = defaultModelCapabilities.supportsFIM + } else { + newSettings.supportsFIM = formValues.supportsFIM; } - if (formValues.reasoningCapabilities !== null) { - if (formValues.reasoningCapabilities) { - settings.reasoningCapabilities = { - supportsReasoning: true, - canTurnOffReasoning: true, - canIOReasoning: true - }; - } else { - settings.reasoningCapabilities = false; + if (formValues.reasoningCapabilities === null) { + newSettings.reasoningCapabilities = defaultModelCapabilities.reasoningCapabilities; + } else if (formValues.reasoningCapabilities) { + const reasoningSettings: any = { + supportsReasoning: true, + canIOReasoning: true, + canTurnOffReasoning: formValues.canTurnOffReasoning + }; + + // Only add these if they have values + if (formValues.reasoningMaxOutputTokens) { + reasoningSettings.reasoningMaxOutputTokens = parseInt(formValues.reasoningMaxOutputTokens); } + + if (formValues.openSourceThinkTags) { + reasoningSettings.openSourceThinkTags = formValues.openSourceThinkTags; + } + + newSettings.reasoningCapabilities = reasoningSettings; + } else { + newSettings.reasoningCapabilities = false; } - await settingsStateService.setOverridesOfModel(providerName, modelName, settings); + await settingsStateService.setOverridesOfModel(providerName, modelName, newSettings); onClose(); }; + + return (
e.stopPropagation()}>
-

Override defaults for {modelName} ({displayInfoOfProviderName(providerName).title})

+

Change Defaults for {modelName} ({displayInfoOfProviderName(providerName).title})

@@ -429,25 +478,51 @@ const ModelSettingsDialog = ({ {/* Context window */}
Context window (tokens) - updateField('contextWindow', value)} - placeholder={(modelCapabilities.contextWindow || defaultModelOptions.contextWindow) + ''} - compact={true} - className="max-w-24" - /> +
+ { + updateField('contextWindow', enabled ? String(defaultModelCapabilities.contextWindow) : ''); + }} + /> + {formValues.contextWindow === '' ? ( + Default ({defaultModelCapabilities.contextWindow}) + ) : ( + updateField('contextWindow', value)} + placeholder={String(defaultModelCapabilities.contextWindow)} + compact={true} + className="max-w-24" + /> + )} +
{/* Maximum output tokens */}
Maximum output tokens - updateField('maxOutputTokens', value)} - placeholder={(modelCapabilities.maxOutputTokens || defaultModelOptions.maxOutputTokens) + ''} - compact={true} - className="max-w-24" - /> +
+ { + updateField('maxOutputTokens', enabled ? String(defaultModelCapabilities.maxOutputTokens) : ''); + }} + /> + {formValues.maxOutputTokens === '' ? ( + Default ({defaultModelCapabilities.maxOutputTokens}) + ) : ( + updateField('maxOutputTokens', value)} + placeholder={String(defaultModelCapabilities.maxOutputTokens)} + compact={true} + className="max-w-24" + /> + )} +
{/* Supports Tools */} @@ -455,10 +530,10 @@ const ModelSettingsDialog = ({ Supports tools updateField('supportsTools', value)} + selectedOption={formValues.specialToolFormat} + onChangeOption={(value) => updateField('specialToolFormat', value)} getOptionDisplayName={(opt) => { - if (opt === '') return `Default (${modelCapabilities.specialToolFormat || 'No'})`; + if (opt === '') return `Default (${defaultModelCapabilities.specialToolFormat || 'No'})`; return opt; }} getOptionDropdownName={(opt) => { @@ -478,7 +553,7 @@ const ModelSettingsDialog = ({ selectedOption={formValues.supportsSystemMessage} onChangeOption={(value) => updateField('supportsSystemMessage', value)} getOptionDisplayName={(opt) => { - if (opt === '') return `Default (${modelCapabilities.supportsSystemMessage || 'No'})`; + if (opt === '') return `Default (${defaultModelCapabilities.supportsSystemMessage || 'No'})`; if (opt === false) return 'No' if (opt === true) return 'Yes' // should never happen return opt; @@ -502,7 +577,7 @@ const ModelSettingsDialog = ({ selectedOption={formValues.supportsFIM} onChangeOption={(value) => updateField('supportsFIM', value)} getOptionDisplayName={(opt) => { - if (opt === null) return `Default (${modelCapabilities.supportsFIM ? 'Yes' : 'No'})`; + if (opt === null) return `Default (${defaultModelCapabilities.supportsFIM ? 'Yes' : 'No'})`; return opt ? 'Yes' : 'No'; }} getOptionDropdownName={(opt) => { @@ -522,7 +597,7 @@ const ModelSettingsDialog = ({ selectedOption={formValues.reasoningCapabilities} onChangeOption={(value) => updateField('reasoningCapabilities', value)} getOptionDisplayName={(opt) => { - if (opt === null) return `Default (${modelCapabilities.reasoningCapabilities ? 'Yes' : 'No'})`; + if (opt === null) return `Default (${defaultModelCapabilities.reasoningCapabilities ? 'Yes' : 'No'})`; return opt ? 'Yes' : 'No'; }} getOptionDropdownName={(opt) => { @@ -533,6 +608,100 @@ const ModelSettingsDialog = ({ className="max-w-32 text-xs" />
+ + {/* Additional reasoning options - only show when reasoning is enabled */} + {formValues.reasoningCapabilities && ( + <> + {/* Can Turn Off Reasoning */} +
+ Allow turning off reasoning + updateField('canTurnOffReasoning', value)} + getOptionDisplayName={(opt) => opt ? 'Yes' : 'No'} + getOptionDropdownName={(opt) => opt ? 'Yes' : 'No'} + getOptionsEqual={(a, b) => a === b} + className="max-w-32 text-xs" + /> +
+ + {/* Reasoning Max Output Tokens - only shown if canTurnOffReasoning is true */} + {formValues.canTurnOffReasoning && ( +
+ Max output tokens when reasoning +
+ { + // Use a reasonable default value when enabling + const defaultValue = defaultModelCapabilities.maxOutputTokens || 500; + updateField('reasoningMaxOutputTokens', enabled ? String(defaultValue) : ''); + }} + /> + {formValues.reasoningMaxOutputTokens === '' ? ( + Default + ) : ( + updateField('reasoningMaxOutputTokens', value)} + placeholder="Default" + compact={true} + className="max-w-24" + /> + )} +
+
+ )} + + {/* Open Source Think Tags Toggle + Input Fields */} +
+ Open source think tags +
+ { + if (enabled) { + // Enable with default values + updateField('openSourceThinkTags', ['', '']); + } else { + // Disable + updateField('openSourceThinkTags', null); + } + }} + /> + + {formValues.openSourceThinkTags !== null && ( +
+ { + const currentTags = formValues.openSourceThinkTags || ['', '']; + updateField('openSourceThinkTags', [value, currentTags[1]]); + }} + placeholder="" + compact={true} + className="max-w-16" + /> + ... + { + const currentTags = formValues.openSourceThinkTags || ['', '']; + updateField('openSourceThinkTags', [currentTags[0], value]); + }} + placeholder="" + compact={true} + className="max-w-16" + /> +
+ )} +
+
+ + )}
@@ -587,8 +756,8 @@ export const ModelDump = () => { const tooltipName = ( disabled ? `Add ${providerTitle} to enable` - : value === true ? 'Enabled' - : 'Disabled' + : value === true ? 'Show in Dropdown' + : 'Hide from Dropdown' ) @@ -601,50 +770,39 @@ export const ModelDump = () => { return
{/* left part is width:full */} -
+
{isNewProviderName ? providerTitle : ''} - {modelName}{detailAboutModel} + {modelName}
+ {/* right part is anything that fits */} -
- - - {/* {type === 'autodetected' ? '(detected locally)' : type === 'default' ? '' : '(custom model)'} */} - - {/* Settings button - only for custom or locally detected models */} - {(type === 'autodetected' || type === 'custom') && ( -
- -
- )} +
+ + {/* Advanced Settings button - only for custom or locally detected models */} +
+ +
+ + {/* Blue star */} + {detailAboutModel} + {/* Switch */} { settingsStateService.toggleModelHidden(providerName, modelName) }} + onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName); }} disabled={disabled} size='sm' @@ -653,8 +811,9 @@ export const ModelDump = () => { data-tooltip-content={tooltipName} /> + {/* X button */}
- {type === 'default' || type === 'autodetected' ? null : } + {type === 'default' || type === 'autodetected' ? null : }
@@ -1301,7 +1460,7 @@ export const Settings = () => { Only works with FIM models.* diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index cc2a4fce..c30df73a 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -173,22 +173,9 @@ export type VoidStaticModelInfo = { // not stateful } -export type ModelOverrideOptions = Partial<{ - contextWindow: number; // input tokens - maxOutputTokens: number; // output tokens, defaults to 4092 - supportsTools: 'openai-style' | undefined; - supportsSystemMessage: 'system-role' | 'developer-role' | false; - supportsFIM: boolean; - reasoningCapabilities: false | { - readonly supportsReasoning: true; - readonly canTurnOffReasoning: boolean; - readonly canIOReasoning: boolean; - readonly reasoningMaxOutputTokens?: number; - readonly openSourceThinkTags?: [string, string]; - } -}> - - +export type ModelOverrideOptions = Partial> @@ -210,7 +197,7 @@ type VoidStaticProviderInfo = { // doesn't change (not stateful) -export const defaultModelOptions = { +const defaultModelOptions = { contextWindow: 4_096, maxOutputTokens: 4_096, cost: { input: 0, output: 0 }, @@ -1155,7 +1142,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi export const getModelCapabilities = ( providerName: ProviderName, modelName: string, - overridesOfModel?: OverridesOfModel + overridesOfModel: OverridesOfModel | undefined ): VoidStaticModelInfo & { modelName: string; isUnrecognizedModel: boolean } => { const lowercaseModelName = modelName.toLowerCase() @@ -1201,8 +1188,9 @@ export const getIsReasoningEnabledState = ( providerName: ProviderName, modelName: string, modelSelectionOptions: ModelSelectionOptions | undefined, + overridesOfModel: OverridesOfModel | undefined, ) => { - const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {} + const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {} if (!supportsReasoning) return false // default to enabled if can't turn off, or if the featureName is Chat. @@ -1213,11 +1201,11 @@ export const getIsReasoningEnabledState = ( } -export const getMaxOutputTokens = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean }) => { +export const getMaxOutputTokens = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean, overridesOfModel: OverridesOfModel | undefined }) => { const { reasoningCapabilities, - maxOutputTokens - } = getModelCapabilities(providerName, modelName) + maxOutputTokens, + } = getModelCapabilities(providerName, modelName, opts.overridesOfModel) return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens } @@ -1227,11 +1215,12 @@ export const getSendableReasoningInfo = ( providerName: ProviderName, modelName: string, modelSelectionOptions: ModelSelectionOptions | undefined, + overridesOfModel: OverridesOfModel | undefined, ): SendableReasoningInfo => { - const { canIOReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {} + const { canIOReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {} if (!canIOReasoning) return null - const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions) + const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) if (!isReasoningEnabled) return null // check for reasoning budget diff --git a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts index d76d6685..6b7cad52 100644 --- a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import { ToolName, ToolParamName } from './prompt/prompts.js' -import { ChatMode, ModelSelection, ModelSelectionOptions, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js' +import { ChatMode, ModelSelection, ModelSelectionOptions, OverridesOfModel, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js' export const errorDetails = (fullError: Error | null): string | null => { @@ -116,6 +116,7 @@ export type ServiceSendLLMMessageParams = { logging: { loggingName: string, loggingExtras?: { [k: string]: any } }; modelSelection: ModelSelection | null; modelSelectionOptions: ModelSelectionOptions | undefined; + overridesOfModel: OverridesOfModel | undefined; onAbort: OnAbort; } & SendLLMType; @@ -129,6 +130,7 @@ export type SendLLMMessageParams = { modelSelection: ModelSelection; modelSelectionOptions: ModelSelectionOptions | undefined; + overridesOfModel: OverridesOfModel | undefined; settingsOfProvider: SettingsOfProvider; } & SendLLMType diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index 462d7899..f8081b9c 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -96,8 +96,15 @@ const _modelsWithSwappedInNewModels = (options: { existingModels: VoidStatefulMo } -export const modelFilterOfFeatureName: { [featureName in FeatureName]: { filter: (o: ModelSelection, opts: { chatMode: ChatMode }) => boolean; emptyMessage: null | { message: string, priority: 'always' | 'fallback' } } } = { - 'Autocomplete': { filter: (o) => getModelCapabilities(o.providerName, o.modelName).supportsFIM, emptyMessage: { message: 'No models support FIM', priority: 'always' } }, +export const modelFilterOfFeatureName: { + [featureName in FeatureName]: { + filter: ( + o: ModelSelection, + opts: { chatMode: ChatMode, overridesOfModel: OverridesOfModel } + ) => boolean; + emptyMessage: null | { message: string, priority: 'always' | 'fallback' } + } } = { + 'Autocomplete': { filter: (o, opts) => getModelCapabilities(o.providerName, o.modelName, opts.overridesOfModel).supportsFIM, emptyMessage: { message: 'No models support FIM', priority: 'always' } }, 'Chat': { filter: o => true, emptyMessage: null, }, 'Ctrl+K': { filter: o => true, emptyMessage: null, }, 'Apply': { filter: o => true, emptyMessage: null, }, @@ -165,7 +172,7 @@ const _validatedModelState = (state: Omit): for (const featureName of featureNames) { const { filter } = modelFilterOfFeatureName[featureName] - const filterOpts = { chatMode: state.globalSettings.chatMode } + const filterOpts = { chatMode: state.globalSettings.chatMode, overridesOfModel: state.overridesOfModel } const modelOptionsForThisFeature = newModelOptions.filter((o) => filter(o.selection, filterOpts)) const modelSelectionAtFeature = newModelSelectionOfFeature[featureName] diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 95b68e45..9b6a51cd 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -15,7 +15,7 @@ import { GoogleAuth } from 'google-auth-library' /* eslint-enable */ import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'; -import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; +import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js'; import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js'; import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js'; @@ -39,6 +39,7 @@ type InternalCommonMessageParams = { providerName: ProviderName; settingsOfProvider: SettingsOfProvider; modelSelectionOptions: ModelSelectionOptions | undefined; + overridesOfModel: OverridesOfModel | undefined; modelName: string; _setAborter: (aborter: () => void) => void; } @@ -144,9 +145,9 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ } -const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, }: SendFIMParams_Internal) => { +const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, overridesOfModel }: SendFIMParams_Internal) => { - const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_) + const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_, overridesOfModel) if (!supportsFIM) { if (modelName === modelName_) onError({ message: `Model ${modelName} does not support FIM.`, fullError: null }) @@ -230,18 +231,18 @@ const rawToolCallObjOf = (name: string, toolParamsStr: string, id: string): RawT // ------------ OPENAI-COMPATIBLE ------------ -const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage }: SendChatParams_Internal) => { +const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage, overridesOfModel }: SendChatParams_Internal) => { const { modelName, specialToolFormat, reasoningCapabilities, - } = getModelCapabilities(providerName, modelName_) + } = getModelCapabilities(providerName, modelName_, overridesOfModel) const { providerReasoningIOSettings } = getProviderCapabilities(providerName) // reasoning const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {} - const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here + const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} // tools @@ -415,21 +416,21 @@ const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBloc } // ------------ ANTHROPIC ------------ -const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => { +const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => { const { modelName, specialToolFormat, - } = getModelCapabilities(providerName, modelName_) + } = getModelCapabilities(providerName, modelName_, overridesOfModel) const thisConfig = settingsOfProvider.anthropic const { providerReasoningIOSettings } = getProviderCapabilities(providerName) // reasoning - const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here + const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} // anthropic-specific - max tokens - const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled }) + const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled, overridesOfModel }) // tools const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null @@ -539,8 +540,8 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag // ------------ MISTRAL ------------ // https://docs.mistral.ai/api/#tag/fim -const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName }: SendFIMParams_Internal) => { - const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_) +const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, overridesOfModel, modelName: modelName_, _setAborter, providerName }: SendFIMParams_Internal) => { + const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_, overridesOfModel) if (!supportsFIM) { if (modelName === modelName_) onError({ message: `Model ${modelName} does not support FIM.`, fullError: null }) @@ -679,6 +680,7 @@ const sendGeminiChat = async ({ onFinalMessage, onError, settingsOfProvider, + overridesOfModel, modelName: modelName_, _setAborter, providerName, @@ -694,13 +696,13 @@ const sendGeminiChat = async ({ modelName, specialToolFormat, // reasoningCapabilities, - } = getModelCapabilities(providerName, modelName_) + } = getModelCapabilities(providerName, modelName_, overridesOfModel) const { providerReasoningIOSettings } = getProviderCapabilities(providerName) // reasoning // const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {} - const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here + const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} // tools diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts index 7c92d9da..969dec8f 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts @@ -20,6 +20,7 @@ export const sendLLMMessage = async ({ settingsOfProvider, modelSelection, modelSelectionOptions, + overridesOfModel, chatMode, separateSystemMessage, }: SendLLMMessageParams, @@ -106,12 +107,12 @@ export const sendLLMMessage = async ({ } const { sendFIM, sendChat } = implementation if (messagesType === 'chatMessages') { - await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage, chatMode }) + await sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName, _setAborter, providerName, separateSystemMessage, chatMode }) return } if (messagesType === 'FIMMessage') { if (sendFIM) { - await sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage }) + await sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, overridesOfModel, modelName, _setAborter, providerName, separateSystemMessage }) return } onError({ message: `Error running Autocomplete with ${providerName} - ${modelName}.`, fullError: null }) From e726c3c5de3df46c7634e3d58711432c4ccf0cbc Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 4 May 2025 19:33:27 -0700 Subject: [PATCH 11/34] add system message trimming because of very small models with 4096 window len --- .../browser/convertToLLMMessageService.ts | 74 ++++++++++++------- .../contrib/void/browser/editCodeService.ts | 9 +-- .../contrib/void/common/modelCapabilities.ts | 2 +- .../llmMessage/extractGrammar.ts | 3 + 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index 2a57ea7b..1ce7996e 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -39,7 +39,7 @@ type SimpleLLMMessage = { const EMPTY_MESSAGE = '(empty message)' const CHARS_PER_TOKEN = 4 -const TRIM_TO_LEN = 60 +const TRIM_TO_LEN = 120 @@ -252,7 +252,7 @@ export type GeminiMessage = { // --- CHAT --- const prepareOpenAIOrAnthropicMessages = ({ - messages, + messages: messages_, systemMessage, aiInstructions, supportsSystemMessage, @@ -270,18 +270,30 @@ const prepareOpenAIOrAnthropicMessages = ({ contextWindow: number, maxOutputTokens: number | null | undefined, }): { messages: AnthropicOrOpenAILLMMessage[], separateSystemMessage: string | undefined } => { + maxOutputTokens = maxOutputTokens ?? 4_096 // default to 4096 + let messages: (SimpleLLMMessage | { role: 'system', content: string })[] = deepClone(messages_) + + // ================ system message ================ + // A COMPLETE HACK: last message is system message for context purposes + + const sysMsgParts: string[] = [] + if (aiInstructions) sysMsgParts.push(`GUIDELINES (from the user's .voidrules file):\n${aiInstructions}`) + if (systemMessage) sysMsgParts.push(systemMessage) + const combinedSystemMessage = sysMsgParts.join('\n\n') + + messages.unshift({ role: 'system', content: combinedSystemMessage }) // ================ trim ================ - - messages = deepClone(messages) messages = messages.map(m => ({ ...m, content: m.role !== 'tool' ? m.content.trim() : m.content })) + type MesType = (typeof messages)[0] + // ================ fit into context ================ // the higher the weight, the higher the desire to truncate - TRIM HIGHEST WEIGHT MESSAGES const alreadyTrimmedIdxes = new Set() - const weight = (message: SimpleLLMMessage, messages: SimpleLLMMessage[], idx: number) => { + const weight = (message: MesType, messages: MesType[], idx: number) => { const base = message.content.length let multiplier: number @@ -289,22 +301,30 @@ const prepareOpenAIOrAnthropicMessages = ({ if (message.role === 'user') { multiplier *= 1 } + else if (message.role === 'system') { + multiplier *= .01 // very low weight + } else { multiplier *= 10 // llm tokens are far less valuable than user tokens } - // 1st message, last 3 msgs, any already modified message should be low in weight - if (idx === 0 || idx >= messages.length - 1 - 3 || alreadyTrimmedIdxes.has(idx)) { + + // any already modified message should not be trimmed again + if (alreadyTrimmedIdxes.has(idx)) { + multiplier = 0 + } + // 1st and last messages should be very low weight + if (idx <= 1 || idx >= messages.length - 1 - 3) { multiplier *= .05 } return base * multiplier } - const _findLargestByWeight = (messages: SimpleLLMMessage[]) => { + const _findLargestByWeight = (messages_: MesType[]) => { let largestIndex = -1 let largestWeight = -Infinity for (let i = 0; i < messages.length; i += 1) { const m = messages[i] - const w = weight(m, messages, i) + const w = weight(m, messages_, i) if (w > largestWeight) { largestWeight = w largestIndex = i @@ -315,7 +335,11 @@ const prepareOpenAIOrAnthropicMessages = ({ let totalLen = 0 for (const m of messages) { totalLen += m.content.length } - const charsNeedToTrim = totalLen - (contextWindow - maxOutputTokens) * CHARS_PER_TOKEN + const charsNeedToTrim = totalLen - Math.max( + (contextWindow - maxOutputTokens) * CHARS_PER_TOKEN, // can be 0, in which case charsNeedToTrim=everything, bad + 4_096 // ensure we don't trim at least 4096 chars (just a random small value) + ) + // <-----------------------------------------> // 0 | | | @@ -335,53 +359,53 @@ const prepareOpenAIOrAnthropicMessages = ({ // if can finish here, do const numCharsWillTrim = m.content.length - TRIM_TO_LEN if (numCharsWillTrim > remainingCharsToTrim) { - m.content = m.content.slice(0, m.content.length - remainingCharsToTrim).trim() + m.content = m.content.slice(0, m.content.length - remainingCharsToTrim - '...'.length).trim() + '...' break } remainingCharsToTrim -= numCharsWillTrim - m.content = m.content.substring(0, TRIM_TO_LEN - 3) + '...' + m.content = m.content.substring(0, TRIM_TO_LEN - '...'.length) + '...' alreadyTrimmedIdxes.add(trimIdx) } + // ================ system message hack ================ + const newSysMsg = messages.shift()!.content + + // ================ tools and anthropicReasoning ================ + // SYSTEM MESSAGE HACK: we shifted (removed) the system message role, so now SimpleLLMMessage[] is valid let llmChatMessages: AnthropicOrOpenAILLMMessage[] = [] if (!specialToolFormat) { // XML tool behavior - llmChatMessages = prepareMessages_XML_tools(messages, supportsAnthropicReasoning) + llmChatMessages = prepareMessages_XML_tools(messages as SimpleLLMMessage[], supportsAnthropicReasoning) } else if (specialToolFormat === 'anthropic-style') { - llmChatMessages = prepareMessages_anthropic_tools(messages, supportsAnthropicReasoning) + llmChatMessages = prepareMessages_anthropic_tools(messages as SimpleLLMMessage[], supportsAnthropicReasoning) } else if (specialToolFormat === 'openai-style') { - llmChatMessages = prepareMessages_openai_tools(messages) + llmChatMessages = prepareMessages_openai_tools(messages as SimpleLLMMessage[]) } const llmMessages = llmChatMessages - // ================ system message concat ================ - - // find system messages and concatenate them - const newSystemMessage = aiInstructions ? - `${(systemMessage ? `${systemMessage}\n\n` : '')}GUIDELINES (from the user's .voidrules file):\n${aiInstructions}` - : systemMessage + // ================ system message add as first llmMessage ================ let separateSystemMessageStr: string | undefined = undefined // if supports system message if (supportsSystemMessage) { if (supportsSystemMessage === 'separated') - separateSystemMessageStr = newSystemMessage + separateSystemMessageStr = newSysMsg else if (supportsSystemMessage === 'system-role') - llmMessages.unshift({ role: 'system', content: newSystemMessage }) // add new first message + llmMessages.unshift({ role: 'system', content: newSysMsg }) // add new first message else if (supportsSystemMessage === 'developer-role') - llmMessages.unshift({ role: 'developer', content: newSystemMessage }) // add new first message + llmMessages.unshift({ role: 'developer', content: newSysMsg }) // add new first message } // if does not support system message else { const newFirstMessage = { role: 'user', - content: `\n${newSystemMessage}\n\n${llmMessages[0].content}` + content: `\n${newSysMsg}\n\n${llmMessages[0].content}` } as const llmMessages.splice(0, 1) // delete first message llmMessages.unshift(newFirstMessage) // add new first message diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 41f45dce..85921a69 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1614,15 +1614,12 @@ class EditCodeService extends Disposable implements IEditCodeService { endLine -= 1 // including newline before start - const contentBeforeStart = startLine !== 0 ? + const origStart = (startLine !== 0 ? modelStrLines.slice(0, startLine).join('\n') + '\n' - : '' + : '').length // including endline at end - const contentUpToEnd = modelStrLines.slice(0, endLine + 1).join('\n') - - const origStart = contentBeforeStart.length; - const origEnd = contentUpToEnd.length; + const origEnd = modelStrLines.slice(0, endLine + 1).join('\n').length - 1 replacements.push({ origStart, origEnd, block: b }); } diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index c30df73a..93cd953e 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -958,7 +958,7 @@ const vLLMSettings: VoidStaticProviderInfo = { const lmStudioSettings: VoidStaticProviderInfo = { providerReasoningIOSettings: { output: { needsManualParse: true }, }, - modelOptionsFallback: (modelName) => extensiveModelFallback(modelName, { downloadable: { sizeGb: 'not-known' } }), + modelOptionsFallback: (modelName) => extensiveModelFallback(modelName, { downloadable: { sizeGb: 'not-known' }, contextWindow: 4_096 }), modelOptions: {}, // TODO } diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts index b90dbafa..e96117b2 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts @@ -23,6 +23,9 @@ export const extractReasoningWrapper = ( let fullTextSoFar = '' let fullReasoningSoFar = '' + + if (!thinkTags[0] || !thinkTags[1]) throw new Error(`thinkTags must not be empty if provided. Got ${JSON.stringify(thinkTags)}.`) + let onText_ = onText onText = (params) => { onText_(params) From 9bebbb5615323bff39befc9660d17194168455a2 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 4 May 2025 18:52:57 -0700 Subject: [PATCH 12/34] draft for accept/reject interactive --- .../contrib/void/browser/editCodeService.ts | 73 +++++++++++++++---- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 85921a69..c3c31f55 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -24,6 +24,7 @@ import { Widget } from '../../../../base/browser/ui/widget.js'; import { URI } from '../../../../base/common/uri.js'; import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js'; import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplaceGivenDescription_systemMessage, searchReplaceGivenDescription_userMessage, tripleTick, } from '../common/prompt/prompts.js'; +import { IVoidCommandBarService } from './voidCommandBarService.js'; import { mountCtrlK } from './react/out/quick-edit-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; @@ -580,7 +581,7 @@ class EditCodeService extends Disposable implements IEditCodeService { } else { throw new Error('Void 1') } - const buttonsWidget = new AcceptRejectInlineWidget({ + const buttonsWidget = this._instantiationService.createInstance(AcceptRejectInlineWidget, { editor, onAccept: () => { this.acceptDiff({ diffid }) @@ -2257,23 +2258,48 @@ registerSingleton(IEditCodeService, EditCodeService, InstantiationType.Eager); class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { - public getId() { return this.ID } - public getDomNode() { return this._domNode; } - public getPosition() { return null } + public getId(): string { + return this.ID || ''; // Ensure we always return a string + } + public getDomNode(): HTMLElement { + return this._domNode; + } + public getPosition() { + return null; + } - private readonly _domNode: HTMLElement; - private readonly editor - private readonly ID - private readonly startLine + private readonly _domNode: HTMLElement; // Using the definite assignment assertion + private readonly editor: ICodeEditor; + private readonly ID: string; + private readonly startLine: number; - constructor({ editor, onAccept, onReject, diffid, startLine, offsetLines }: { editor: ICodeEditor; onAccept: () => void; onReject: () => void; diffid: string, startLine: number, offsetLines: number }) { - super() + constructor( + { editor, onAccept, onReject, diffid, startLine, offsetLines }: { + editor: ICodeEditor; + onAccept: () => void; + onReject: () => void; + diffid: string, + startLine: number, + offsetLines: number + }, + @IVoidCommandBarService private readonly _voidCommandBarService: IVoidCommandBarService + ) { + super(); - - this.ID = editor.getModel()?.uri.fsPath + diffid; + const uri = editor.getModel()?.uri; + // Initialize with default values + this.ID = '' this.editor = editor; this.startLine = startLine; + if (!uri) { + const { dummyDiv } = dom.h('div@dummyDiv'); + this._domNode = dummyDiv + return; + } + + this.ID = uri.fsPath + diffid; + const lineHeight = editor.getOption(EditorOption.lineHeight); // Create container div with buttons @@ -2354,6 +2380,25 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { this._register(editor.onDidChangeModelContent(e => { updateTop() })) this._register(editor.onDidLayoutChange(e => { updateTop(); updateLeft() })) + + // Listen for state changes in the command bar service + this._register(this._voidCommandBarService.onDidChangeState(e => { + if (uri && e.uri.fsPath === uri.fsPath) { + const commandBarStateAtUri = this._voidCommandBarService.stateOfURI[uri.fsPath]; + const selectedDiffIdx = commandBarStateAtUri?.diffIdx ?? null; + const thisDiffIdx = commandBarStateAtUri?.sortedDiffIds.indexOf(diffid) ?? null; + + // Update button text based on styles + if (thisDiffIdx !== null && selectedDiffIdx === thisDiffIdx) { + acceptButton.textContent = 'Accept'; + rejectButton.textContent = 'Reject'; + } else { + acceptButton.textContent = 'Accept'; + rejectButton.textContent = 'Reject'; + } + } + })); + // mount this widget editor.addOverlayWidget(this); @@ -2361,8 +2406,8 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { } public override dispose(): void { - this.editor.removeOverlayWidget(this) - super.dispose() + this.editor.removeOverlayWidget(this); + super.dispose(); } } From c088ad3c07c09c717e39289ef6410f08e73801d4 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sun, 4 May 2025 23:46:13 -0700 Subject: [PATCH 13/34] keyboard shortcuts for accept/reject --- .../contrib/void/browser/actionIDs.ts | 4 + .../contrib/void/browser/editCodeService.ts | 59 ++++++++++---- .../void/browser/editCodeServiceInterface.ts | 2 + .../void/browser/voidCommandBarService.ts | 79 +++++++++++++++++++ 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/actionIDs.ts b/src/vs/workbench/contrib/void/browser/actionIDs.ts index b237ecf8..5efe6b25 100644 --- a/src/vs/workbench/contrib/void/browser/actionIDs.ts +++ b/src/vs/workbench/contrib/void/browser/actionIDs.ts @@ -4,3 +4,7 @@ export const VOID_CTRL_L_ACTION_ID = 'void.ctrlLAction' export const VOID_CTRL_K_ACTION_ID = 'void.ctrlKAction' + +export const VOID_ACCEPT_DIFF_ACTION_ID = 'void.acceptDiff' + +export const VOID_REJECT_DIFF_ACTION_ID = 'void.rejectDiff' diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index c3c31f55..2cc7d942 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -25,6 +25,8 @@ import { URI } from '../../../../base/common/uri.js'; import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js'; import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplaceGivenDescription_systemMessage, searchReplaceGivenDescription_userMessage, tripleTick, } from '../common/prompt/prompts.js'; import { IVoidCommandBarService } from './voidCommandBarService.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { VOID_ACCEPT_DIFF_ACTION_ID, VOID_REJECT_DIFF_ACTION_ID } from './actionIDs.js'; import { mountCtrlK } from './react/out/quick-edit-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; @@ -2252,9 +2254,12 @@ registerSingleton(IEditCodeService, EditCodeService, InstantiationType.Eager); +const processRawKeybindingText = (keybindingStr: string) => { + return keybindingStr + .replace(/Enter/g, '↵') // ⏎ + .replace(/Backspace/g, '⌫') - - +} class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { @@ -2282,7 +2287,8 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { startLine: number, offsetLines: number }, - @IVoidCommandBarService private readonly _voidCommandBarService: IVoidCommandBarService + @IVoidCommandBarService private readonly _voidCommandBarService: IVoidCommandBarService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); @@ -2302,6 +2308,27 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { const lineHeight = editor.getOption(EditorOption.lineHeight); + const getAcceptRejectText = () => { + const acceptKeybinding = this._keybindingService.lookupKeybinding(VOID_ACCEPT_DIFF_ACTION_ID); + const rejectKeybinding = this._keybindingService.lookupKeybinding(VOID_REJECT_DIFF_ACTION_ID); + + const acceptKeybindLabel = processRawKeybindingText(acceptKeybinding && acceptKeybinding.getLabel() || ''); + const rejectKeybindLabel = processRawKeybindingText(rejectKeybinding && rejectKeybinding.getLabel() || '') + + const commandBarStateAtUri = this._voidCommandBarService.stateOfURI[uri.fsPath]; + const selectedDiffIdx = commandBarStateAtUri?.diffIdx ?? 0; // 0th item is selected by default + const thisDiffIdx = commandBarStateAtUri?.sortedDiffIds.indexOf(diffid) ?? null; + + const showLabel = thisDiffIdx === selectedDiffIdx + + const acceptText = `Accept${showLabel ? ` ` + acceptKeybindLabel : ''}`; + const rejectText = `Reject${showLabel ? ` ` + rejectKeybindLabel : ''}`; + + return { acceptText, rejectText } + } + + const { acceptText, rejectText } = getAcceptRejectText() + // Create container div with buttons const { acceptButton, rejectButton, buttons } = dom.h('div@buttons', [ dom.h('button@acceptButton', []), @@ -2315,11 +2342,14 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { buttons.style.paddingRight = '4px'; buttons.style.zIndex = '1'; buttons.style.transform = `translateY(${offsetLines * lineHeight}px)`; + buttons.style.justifyContent = 'flex-end'; + buttons.style.width = '100%'; + buttons.style.pointerEvents = 'none'; // Style accept button acceptButton.onclick = onAccept; - acceptButton.textContent = 'Accept'; + acceptButton.textContent = acceptText; acceptButton.style.backgroundColor = acceptBg; acceptButton.style.border = acceptBorder; acceptButton.style.color = buttonTextColor; @@ -2333,10 +2363,12 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { acceptButton.style.cursor = 'pointer'; acceptButton.style.height = '100%'; acceptButton.style.boxShadow = '0 2px 3px rgba(0,0,0,0.2)'; + acceptButton.style.pointerEvents = 'auto'; + // Style reject button rejectButton.onclick = onReject; - rejectButton.textContent = 'Reject'; + rejectButton.textContent = rejectText; rejectButton.style.backgroundColor = rejectBg; rejectButton.style.border = rejectBorder; rejectButton.style.color = buttonTextColor; @@ -2350,6 +2382,7 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { rejectButton.style.cursor = 'pointer'; rejectButton.style.height = '100%'; rejectButton.style.boxShadow = '0 2px 3px rgba(0,0,0,0.2)'; + rejectButton.style.pointerEvents = 'auto'; @@ -2384,18 +2417,12 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget { // Listen for state changes in the command bar service this._register(this._voidCommandBarService.onDidChangeState(e => { if (uri && e.uri.fsPath === uri.fsPath) { - const commandBarStateAtUri = this._voidCommandBarService.stateOfURI[uri.fsPath]; - const selectedDiffIdx = commandBarStateAtUri?.diffIdx ?? null; - const thisDiffIdx = commandBarStateAtUri?.sortedDiffIds.indexOf(diffid) ?? null; - // Update button text based on styles - if (thisDiffIdx !== null && selectedDiffIdx === thisDiffIdx) { - acceptButton.textContent = 'Accept'; - rejectButton.textContent = 'Reject'; - } else { - acceptButton.textContent = 'Accept'; - rejectButton.textContent = 'Reject'; - } + const { acceptText, rejectText } = getAcceptRejectText() + + acceptButton.textContent = acceptText; + rejectButton.textContent = rejectText; + } })); diff --git a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts index 26ff9d2b..4d7fafd6 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeServiceInterface.ts @@ -54,6 +54,8 @@ export interface IEditCodeService { diffOfId: Record; acceptOrRejectAllDiffAreas(opts: { uri: URI, removeCtrlKs: boolean, behavior: 'reject' | 'accept', _addToHistory?: boolean }): void; + acceptDiff({ diffid }: { diffid: number }): void; + rejectDiff({ diffid }: { diffid: number }): void; // events onDidAddOrDeleteDiffZones: Event<{ uri: URI }>; diff --git a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts index e26bf5c9..63e66f69 100644 --- a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts +++ b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts @@ -18,6 +18,14 @@ import { IEditCodeService } from './editCodeServiceInterface.js'; import { ITextModel } from '../../../../editor/common/model.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { generateUuid } from '../../../../base/common/uuid.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { VOID_ACCEPT_DIFF_ACTION_ID, VOID_REJECT_DIFF_ACTION_ID } from './actionIDs.js'; +import { localize2 } from '../../../../nls.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { IMetricsService } from '../common/metricsService.js'; +import { KeyMod } from '../../../../editor/common/services/editorBaseApi.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; @@ -456,3 +464,74 @@ class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget { super.dispose(); } } + + +registerAction2(class extends Action2 { + constructor() { + super({ + id: VOID_ACCEPT_DIFF_ACTION_ID, + f1: true, + title: localize2('voidAcceptDiffAction', 'Void: Accept Diff'), + keybinding: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.VoidExtension, + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editCodeService = accessor.get(IEditCodeService); + const commandBarService = accessor.get(IVoidCommandBarService); + const metricsService = accessor.get(IMetricsService); + + + const activeURI = commandBarService.activeURI; + if (!activeURI) return; + + const commandBarState = commandBarService.stateOfURI[activeURI.fsPath]; + if (!commandBarState) return; + const diffIdx = commandBarState.diffIdx ?? 0; + + const diffid = commandBarState.sortedDiffIds[diffIdx]; + if (!diffid) return; + + metricsService.capture('Accept Diff', { diffid, keyboard: true }); + editCodeService.acceptDiff({ diffid: parseInt(diffid) }) + + } +}); + + + +registerAction2(class extends Action2 { + constructor() { + super({ + id: VOID_REJECT_DIFF_ACTION_ID, + f1: true, + title: localize2('voidRejectDiffAction', 'Void: Reject Diff'), + keybinding: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Backspace, + weight: KeybindingWeight.VoidExtension, + } + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editCodeService = accessor.get(IEditCodeService); + const commandBarService = accessor.get(IVoidCommandBarService); + const metricsService = accessor.get(IMetricsService); + + const activeURI = commandBarService.activeURI; + if (!activeURI) return; + + const commandBarState = commandBarService.stateOfURI[activeURI.fsPath]; + if (!commandBarState) return; + const diffIdx = commandBarState.diffIdx ?? 0; + + const diffid = commandBarState.sortedDiffIds[diffIdx]; + if (!diffid) return; + + metricsService.capture('Reject Diff', { diffid, keyboard: true }); + editCodeService.rejectDiff({ diffid: parseInt(diffid) }) + } +}); From 09d7e7f3e6ebeb60ed706f60eb037116839ce7a6 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 5 May 2025 00:56:58 -0700 Subject: [PATCH 14/34] accept/reject better keybind --- .../react/src/void-editor-widgets-tsx/VoidCommandBar.tsx | 9 +++++---- .../contrib/void/browser/voidCommandBarService.ts | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx index 40b34d14..86b54c67 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx @@ -12,6 +12,7 @@ import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'; import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js'; import { VoidCommandBarProps } from '../../../voidCommandBarService.js'; import { AcceptAllButtonWrapper, RejectAllButtonWrapper } from '../sidebar-tsx/SidebarChat.js'; +import { MoveDown, MoveLeft, MoveRight, MoveUp } from 'lucide-react'; export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => { const isDark = useIsDark() @@ -148,7 +149,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => { goToDiffIdx(prevDiffIdx); } }} - >↑ + > const downButton = + > const leftButton = + > const rightButton = + > diff --git a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts index 63e66f69..4913e681 100644 --- a/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts +++ b/src/vs/workbench/contrib/void/browser/voidCommandBarService.ts @@ -473,7 +473,8 @@ registerAction2(class extends Action2 { f1: true, title: localize2('voidAcceptDiffAction', 'Void: Accept Diff'), keybinding: { - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.Enter, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter }, weight: KeybindingWeight.VoidExtension, } }); @@ -510,7 +511,8 @@ registerAction2(class extends Action2 { f1: true, title: localize2('voidRejectDiffAction', 'Void: Reject Diff'), keybinding: { - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Backspace, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.Backspace, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace }, weight: KeybindingWeight.VoidExtension, } }); From 25c12b101fda8bd54aa370ce8fa27d943017da62 Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Mon, 5 May 2025 01:10:10 -0700 Subject: [PATCH 15/34] small --- .../void-editor-widgets-tsx/VoidCommandBar.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx index 86b54c67..6b94a4e2 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx @@ -138,7 +138,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => { const upButton = + > const downButton = + > const leftButton = + > const rightButton = + > From 4dae90e0476c2acb4452f2e73719c0faf82f573c Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sun, 4 May 2025 19:52:23 -0700 Subject: [PATCH 16/34] maxOutputTokens -> reservedOutputTokenSpace --- .../browser/convertToLLMMessageService.ts | 20 +-- .../src/void-onboarding/VoidOnboarding.tsx | 2 +- .../react/src/void-settings-tsx/Settings.tsx | 54 +++--- .../contrib/void/common/modelCapabilities.ts | 162 +++++++++--------- .../llmMessage/sendLLMMessage.impl.ts | 4 +- 5 files changed, 121 insertions(+), 121 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index 1ce7996e..27c054ce 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -6,7 +6,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ChatMessage } from '../common/chatThreadServiceTypes.js'; -import { getIsReasoningEnabledState, getMaxOutputTokens, getModelCapabilities } from '../common/modelCapabilities.js'; +import { getIsReasoningEnabledState, getReservedOutputTokenSpace, getModelCapabilities } from '../common/modelCapabilities.js'; import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/prompt/prompts.js'; import { AnthropicLLMChatMessage, AnthropicReasoning, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; @@ -259,7 +259,7 @@ const prepareOpenAIOrAnthropicMessages = ({ specialToolFormat, supportsAnthropicReasoning, contextWindow, - maxOutputTokens, + reservedOutputTokenSpace, }: { messages: SimpleLLMMessage[], systemMessage: string, @@ -268,10 +268,10 @@ const prepareOpenAIOrAnthropicMessages = ({ specialToolFormat: 'openai-style' | 'anthropic-style' | undefined, supportsAnthropicReasoning: boolean, contextWindow: number, - maxOutputTokens: number | null | undefined, + reservedOutputTokenSpace: number | null | undefined, }): { messages: AnthropicOrOpenAILLMMessage[], separateSystemMessage: string | undefined } => { - maxOutputTokens = maxOutputTokens ?? 4_096 // default to 4096 + reservedOutputTokenSpace = reservedOutputTokenSpace ?? 4_096 // default to 4096 let messages: (SimpleLLMMessage | { role: 'system', content: string })[] = deepClone(messages_) // ================ system message ================ @@ -336,7 +336,7 @@ const prepareOpenAIOrAnthropicMessages = ({ let totalLen = 0 for (const m of messages) { totalLen += m.content.length } const charsNeedToTrim = totalLen - Math.max( - (contextWindow - maxOutputTokens) * CHARS_PER_TOKEN, // can be 0, in which case charsNeedToTrim=everything, bad + (contextWindow - reservedOutputTokenSpace) * CHARS_PER_TOKEN, // can be 0, in which case charsNeedToTrim=everything, bad 4_096 // ensure we don't trim at least 4096 chars (just a random small value) ) @@ -494,7 +494,7 @@ const prepareMessages = (params: { specialToolFormat: 'openai-style' | 'anthropic-style' | 'gemini-style' | undefined, supportsAnthropicReasoning: boolean, contextWindow: number, - maxOutputTokens: number | null | undefined, + reservedOutputTokenSpace: number | null | undefined, providerName: ProviderName }): { messages: LLMChatMessage[], separateSystemMessage: string | undefined } => { @@ -647,7 +647,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess const aiInstructions = this._getCombinedAIInstructions(); const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) - const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled, overridesOfModel }) + const reservedOutputTokenSpace = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled, overridesOfModel }) const { messages, separateSystemMessage } = prepareMessages({ messages: simpleMessages, @@ -657,7 +657,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess specialToolFormat, supportsAnthropicReasoning: providerName === 'anthropic', contextWindow, - maxOutputTokens, + reservedOutputTokenSpace, providerName, }) return { messages, separateSystemMessage }; @@ -681,7 +681,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess const aiInstructions = this._getCombinedAIInstructions(); const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions, overridesOfModel) - const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled, overridesOfModel }) + const reservedOutputTokenSpace = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled, overridesOfModel }) const llmMessages = this._chatMessagesToSimpleMessages(chatMessages) const { messages, separateSystemMessage } = prepareMessages({ @@ -692,7 +692,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess specialToolFormat, supportsAnthropicReasoning: providerName === 'anthropic', contextWindow, - maxOutputTokens, + reservedOutputTokenSpace, providerName, }) return { messages, separateSystemMessage }; diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx index 81a76547..bf8a3223 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -364,7 +364,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName contextWindow, isUnrecognizedModel, - maxOutputTokens, + reservedOutputTokenSpace, supportsSystemMessage, } = capabilities diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index de93ce27..20a4e9d0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -329,24 +329,24 @@ const ModelSettingsDialog = ({ // Initialize form state for all potential override options const [formValues, setFormValues] = useState<{ contextWindow: string; - maxOutputTokens: string; + reservedOutputTokenSpace: string; specialToolFormat: 'openai-style' | 'gemini-style' | 'anthropic-style' | undefined | ''; supportsSystemMessage: 'system-role' | 'developer-role' | 'separated' | false | ''; supportsFIM: boolean | null; reasoningCapabilities: boolean | null; canTurnOffReasoning: boolean; - reasoningMaxOutputTokens: string; + reasoningReservedOutputTokenSpace: string; openSourceThinkTags: [string, string] | null; }>({ // start form as default values contextWindow: '', - maxOutputTokens: '', + reservedOutputTokenSpace: '', specialToolFormat: '', supportsSystemMessage: '', supportsFIM: null, reasoningCapabilities: null, canTurnOffReasoning: false, - reasoningMaxOutputTokens: '', + reasoningReservedOutputTokenSpace: '', openSourceThinkTags: null, }); @@ -370,15 +370,15 @@ const ModelSettingsDialog = ({ // to indicate default values should be used setFormValues({ contextWindow: overrides.contextWindow !== undefined ? String(overrides.contextWindow) : '', - maxOutputTokens: overrides.maxOutputTokens !== undefined ? String(overrides.maxOutputTokens) : '', + reservedOutputTokenSpace: overrides.reservedOutputTokenSpace !== undefined ? String(overrides.reservedOutputTokenSpace) : '', specialToolFormat: overrides.specialToolFormat !== undefined ? overrides.specialToolFormat : '', supportsSystemMessage: overrides.supportsSystemMessage !== undefined ? overrides.supportsSystemMessage : '', supportsFIM: overrides.supportsFIM !== undefined ? overrides.supportsFIM : null, reasoningCapabilities: overrides.reasoningCapabilities !== undefined ? !!overrides.reasoningCapabilities : null, canTurnOffReasoning: typeof reasoningCapabilities === 'object' ? !!reasoningCapabilities.canTurnOffReasoning : false, - reasoningMaxOutputTokens: typeof reasoningCapabilities === 'object' && reasoningCapabilities.reasoningMaxOutputTokens ? - String(reasoningCapabilities.reasoningMaxOutputTokens) : '', + reasoningReservedOutputTokenSpace: typeof reasoningCapabilities === 'object' && reasoningCapabilities.reasoningReservedOutputTokenSpace ? + String(reasoningCapabilities.reasoningReservedOutputTokenSpace) : '', openSourceThinkTags: thinkTags, }); } @@ -406,11 +406,11 @@ const ModelSettingsDialog = ({ if (!isNaN(tokens)) newSettings.contextWindow = tokens; } - if (formValues.maxOutputTokens.trim() === '') { - newSettings.maxOutputTokens = defaultModelCapabilities.maxOutputTokens; - } else if (formValues.maxOutputTokens) { - const tokens = parseInt(formValues.maxOutputTokens); - if (!isNaN(tokens)) newSettings.maxOutputTokens = tokens; + if (formValues.reservedOutputTokenSpace.trim() === '') { + newSettings.reservedOutputTokenSpace = defaultModelCapabilities.reservedOutputTokenSpace; + } else if (formValues.reservedOutputTokenSpace) { + const tokens = parseInt(formValues.reservedOutputTokenSpace); + if (!isNaN(tokens)) newSettings.reservedOutputTokenSpace = tokens; } // Handle dropdown fields @@ -442,8 +442,8 @@ const ModelSettingsDialog = ({ }; // Only add these if they have values - if (formValues.reasoningMaxOutputTokens) { - reasoningSettings.reasoningMaxOutputTokens = parseInt(formValues.reasoningMaxOutputTokens); + if (formValues.reasoningReservedOutputTokenSpace) { + reasoningSettings.reasoningReservedOutputTokenSpace = parseInt(formValues.reasoningReservedOutputTokenSpace); } if (formValues.openSourceThinkTags) { @@ -506,18 +506,18 @@ const ModelSettingsDialog = ({
{ - updateField('maxOutputTokens', enabled ? String(defaultModelCapabilities.maxOutputTokens) : ''); + updateField('reservedOutputTokenSpace', enabled ? String(defaultModelCapabilities.reservedOutputTokenSpace) : ''); }} /> - {formValues.maxOutputTokens === '' ? ( - Default ({defaultModelCapabilities.maxOutputTokens}) + {formValues.reservedOutputTokenSpace === '' ? ( + Default ({defaultModelCapabilities.reservedOutputTokenSpace}) ) : ( updateField('maxOutputTokens', value)} - placeholder={String(defaultModelCapabilities.maxOutputTokens)} + value={formValues.reservedOutputTokenSpace} + onChangeValue={(value) => updateField('reservedOutputTokenSpace', value)} + placeholder={String(defaultModelCapabilities.reservedOutputTokenSpace)} compact={true} className="max-w-24" /> @@ -633,19 +633,19 @@ const ModelSettingsDialog = ({
{ // Use a reasonable default value when enabling - const defaultValue = defaultModelCapabilities.maxOutputTokens || 500; - updateField('reasoningMaxOutputTokens', enabled ? String(defaultValue) : ''); + const defaultValue = defaultModelCapabilities.reservedOutputTokenSpace || 500; + updateField('reasoningReservedOutputTokenSpace', enabled ? String(defaultValue) : ''); }} /> - {formValues.reasoningMaxOutputTokens === '' ? ( + {formValues.reasoningReservedOutputTokenSpace === '' ? ( Default ) : ( updateField('reasoningMaxOutputTokens', value)} + value={formValues.reasoningReservedOutputTokenSpace} + onChangeValue={(value) => updateField('reasoningReservedOutputTokenSpace', value)} placeholder="Default" compact={true} className="max-w-24" diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 93cd953e..455b9e78 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -141,7 +141,7 @@ export const defaultModelsOfProvider = { export type VoidStaticModelInfo = { // not stateful contextWindow: number; // input tokens - maxOutputTokens: number | null; // output tokens, defaults to 4092 + reservedOutputTokenSpace: number | null; // output tokens, defaults to 4092 cost: { // <-- UNUSED input: number; output: number; @@ -162,7 +162,7 @@ export type VoidStaticModelInfo = { // not stateful // reasoning options if supports reasoning readonly canTurnOffReasoning: boolean; // whether or not the user can disable reasoning mode (false if the model only supports reasoning) readonly canIOReasoning: boolean; // whether or not the model actually outputs reasoning (eg o1 lets us control reasoning but not output it) - readonly reasoningMaxOutputTokens?: number; // overrides normal maxOutputTokens + readonly reasoningReservedOutputTokenSpace?: number; // overrides normal reservedOutputTokenSpace readonly reasoningBudgetSlider?: { type: 'slider'; min: number; max: number; default: number }; // options related specifically to model output @@ -174,7 +174,7 @@ export type VoidStaticModelInfo = { // not stateful export type ModelOverrideOptions = Partial> @@ -199,7 +199,7 @@ type VoidStaticProviderInfo = { // doesn't change (not stateful) const defaultModelOptions = { contextWindow: 4_096, - maxOutputTokens: 4_096, + reservedOutputTokenSpace: 4_096, cost: { input: 0, output: 0 }, downloadable: false, supportsSystemMessage: false, @@ -215,57 +215,57 @@ const openSourceModelOptions_assumingOAICompat = { supportsFIM: false, supportsSystemMessage: false, reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, openSourceThinkTags: ['', ''] }, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'deepseekCoderV3': { supportsFIM: false, supportsSystemMessage: false, // unstable reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'deepseekCoderV2': { supportsFIM: false, supportsSystemMessage: false, // unstable reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'codestral': { supportsFIM: true, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'openhands-lm-32b': { // https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, // built on qwen 2.5 32B instruct - contextWindow: 128_000, maxOutputTokens: 4_096 + contextWindow: 128_000, reservedOutputTokenSpace: 4_096 }, 'phi4': { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 16_000, maxOutputTokens: 4_096, + contextWindow: 16_000, reservedOutputTokenSpace: 4_096, }, 'gemma': { // https://news.ycombinator.com/item?id=43451406 supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, // llama 4 https://ai.meta.com/blog/llama-4-multimodal-intelligence/ 'llama4-scout': { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 10_000_000, maxOutputTokens: 4_096, + contextWindow: 10_000_000, reservedOutputTokenSpace: 4_096, }, 'llama4-maverick': { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 10_000_000, maxOutputTokens: 4_096, + contextWindow: 10_000_000, reservedOutputTokenSpace: 4_096, }, // llama 3 @@ -273,65 +273,65 @@ const openSourceModelOptions_assumingOAICompat = { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'llama3.1': { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'llama3.2': { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'llama3.3': { supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, // qwen 'qwen2.5coder': { supportsFIM: true, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 32_000, maxOutputTokens: 4_096, + contextWindow: 32_000, reservedOutputTokenSpace: 4_096, }, 'qwq': { supportsFIM: false, // no FIM, yes reasoning supportsSystemMessage: 'system-role', reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, openSourceThinkTags: ['', ''] }, - contextWindow: 128_000, maxOutputTokens: 8_192, + contextWindow: 128_000, reservedOutputTokenSpace: 8_192, }, 'qwen3': { supportsFIM: false, // replaces QwQ supportsSystemMessage: 'system-role', reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: true, canIOReasoning: true, openSourceThinkTags: ['', ''] }, - contextWindow: 32_768, maxOutputTokens: 8_192, + contextWindow: 32_768, reservedOutputTokenSpace: 8_192, }, // FIM only 'starcoder2': { supportsFIM: true, supportsSystemMessage: false, reasoningCapabilities: false, - contextWindow: 128_000, maxOutputTokens: 8_192, + contextWindow: 128_000, reservedOutputTokenSpace: 8_192, }, 'codegemma:2b': { supportsFIM: true, supportsSystemMessage: false, reasoningCapabilities: false, - contextWindow: 128_000, maxOutputTokens: 8_192, + contextWindow: 128_000, reservedOutputTokenSpace: 8_192, }, 'quasar': { // openrouter/quasar-alpha supportsFIM: false, supportsSystemMessage: 'system-role', reasoningCapabilities: false, - contextWindow: 1_000_000, maxOutputTokens: 32_000, + contextWindow: 1_000_000, reservedOutputTokenSpace: 32_000, } } as const satisfies { [s: string]: Partial } @@ -416,7 +416,7 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = ( const anthropicModelOptions = { 'claude-3-7-sonnet-20250219': { // https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table contextWindow: 200_000, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 }, downloadable: false, supportsFIM: false, @@ -426,14 +426,14 @@ const anthropicModelOptions = { supportsReasoning: true, canTurnOffReasoning: true, canIOReasoning: true, - reasoningMaxOutputTokens: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19 + reasoningReservedOutputTokenSpace: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19 reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 }, }, 'claude-3-5-sonnet-20241022': { contextWindow: 200_000, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 }, downloadable: false, supportsFIM: false, @@ -443,7 +443,7 @@ const anthropicModelOptions = { }, 'claude-3-5-haiku-20241022': { contextWindow: 200_000, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 0.80, cache_read: 0.08, cache_write: 1.00, output: 4.00 }, downloadable: false, supportsFIM: false, @@ -453,7 +453,7 @@ const anthropicModelOptions = { }, 'claude-3-opus-20240229': { contextWindow: 200_000, - maxOutputTokens: 4_096, + reservedOutputTokenSpace: 4_096, cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 75.00 }, downloadable: false, supportsFIM: false, @@ -464,7 +464,7 @@ const anthropicModelOptions = { 'claude-3-sonnet-20240229': { // no point of using this, but including this for people who put it in contextWindow: 200_000, cost: { input: 3.00, output: 15.00 }, downloadable: false, - maxOutputTokens: 4_096, + reservedOutputTokenSpace: 4_096, supportsFIM: false, specialToolFormat: 'anthropic-style', supportsSystemMessage: 'separated', @@ -493,7 +493,7 @@ const anthropicSettings: VoidStaticProviderInfo = { if (lower.includes('claude-3-opus')) fallbackName = 'claude-3-opus-20240229' if (lower.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229' if (fallbackName) return { modelName: fallbackName, ...anthropicModelOptions[fallbackName] } - return { modelName, ...defaultModelOptions, maxOutputTokens: 4_096 } + return { modelName, ...defaultModelOptions, reservedOutputTokenSpace: 4_096 } }, } @@ -502,7 +502,7 @@ const anthropicSettings: VoidStaticProviderInfo = { const openAIModelOptions = { // https://platform.openai.com/docs/pricing 'o3': { contextWindow: 1_047_576, - maxOutputTokens: 32_768, + reservedOutputTokenSpace: 32_768, cost: { input: 10.00, output: 40.00, cache_read: 2.50 }, downloadable: false, supportsFIM: false, @@ -512,7 +512,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'o4-mini': { contextWindow: 1_047_576, - maxOutputTokens: 32_768, + reservedOutputTokenSpace: 32_768, cost: { input: 1.10, output: 4.40, cache_read: 0.275 }, downloadable: false, supportsFIM: false, @@ -522,7 +522,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'gpt-4.1': { contextWindow: 1_047_576, - maxOutputTokens: 32_768, + reservedOutputTokenSpace: 32_768, cost: { input: 2.00, output: 8.00, cache_read: 0.50 }, downloadable: false, supportsFIM: false, @@ -532,7 +532,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'gpt-4.1-mini': { contextWindow: 1_047_576, - maxOutputTokens: 32_768, + reservedOutputTokenSpace: 32_768, cost: { input: 0.40, output: 1.60, cache_read: 0.10 }, downloadable: false, supportsFIM: false, @@ -542,7 +542,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'gpt-4.1-nano': { contextWindow: 1_047_576, - maxOutputTokens: 32_768, + reservedOutputTokenSpace: 32_768, cost: { input: 0.10, output: 0.40, cache_read: 0.03 }, downloadable: false, supportsFIM: false, @@ -552,7 +552,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'o1': { contextWindow: 128_000, - maxOutputTokens: 100_000, + reservedOutputTokenSpace: 100_000, cost: { input: 15.00, cache_read: 7.50, output: 60.00, }, downloadable: false, supportsFIM: false, @@ -561,7 +561,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'o3-mini': { contextWindow: 200_000, - maxOutputTokens: 100_000, + reservedOutputTokenSpace: 100_000, cost: { input: 1.10, cache_read: 0.55, output: 4.40, }, downloadable: false, supportsFIM: false, @@ -570,7 +570,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'gpt-4o': { contextWindow: 128_000, - maxOutputTokens: 16_384, + reservedOutputTokenSpace: 16_384, cost: { input: 2.50, cache_read: 1.25, output: 10.00, }, downloadable: false, supportsFIM: false, @@ -580,7 +580,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'o1-mini': { contextWindow: 128_000, - maxOutputTokens: 65_536, + reservedOutputTokenSpace: 65_536, cost: { input: 1.10, cache_read: 0.55, output: 4.40, }, downloadable: false, supportsFIM: false, @@ -589,7 +589,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing }, 'gpt-4o-mini': { contextWindow: 128_000, - maxOutputTokens: 16_384, + reservedOutputTokenSpace: 16_384, cost: { input: 0.15, cache_read: 0.075, output: 0.60, }, downloadable: false, supportsFIM: false, @@ -617,7 +617,7 @@ const openAISettings: VoidStaticProviderInfo = { const xAIModelOptions = { 'grok-2': { contextWindow: 131_072, - maxOutputTokens: null, // 131_072, + reservedOutputTokenSpace: null, // 131_072, cost: { input: 2.00, output: 10.00 }, downloadable: false, supportsFIM: false, @@ -626,7 +626,7 @@ const xAIModelOptions = { }, // 'grok-3': { // contextWindow: 1_000_000, - // maxOutputTokens: null, + // reservedOutputTokenSpace: null, // cost: {}, // downloadable: false, // supportsFIM: false, @@ -651,7 +651,7 @@ const xAISettings: VoidStaticProviderInfo = { const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing 'gemini-2.5-flash-preview-04-17': { contextWindow: 1_048_576, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 0.15, output: .60 }, // TODO $3.50 output with thinking not included downloadable: false, supportsFIM: false, @@ -661,7 +661,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing }, 'gemini-2.5-pro-exp-03-25': { contextWindow: 1_048_576, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 0, output: 0 }, downloadable: false, supportsFIM: false, @@ -671,7 +671,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing }, 'gemini-2.0-flash': { contextWindow: 1_048_576, - maxOutputTokens: 8_192, // 8_192, + reservedOutputTokenSpace: 8_192, // 8_192, cost: { input: 0.10, output: 0.40 }, downloadable: false, supportsFIM: false, @@ -681,7 +681,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing }, 'gemini-2.0-flash-lite-preview-02-05': { contextWindow: 1_048_576, - maxOutputTokens: 8_192, // 8_192, + reservedOutputTokenSpace: 8_192, // 8_192, cost: { input: 0.075, output: 0.30 }, downloadable: false, supportsFIM: false, @@ -691,7 +691,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing }, 'gemini-1.5-flash': { contextWindow: 1_048_576, - maxOutputTokens: 8_192, // 8_192, + reservedOutputTokenSpace: 8_192, // 8_192, cost: { input: 0.075, output: 0.30 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now downloadable: false, supportsFIM: false, @@ -701,7 +701,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing }, 'gemini-1.5-pro': { contextWindow: 2_097_152, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 1.25, output: 5.00 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now downloadable: false, supportsFIM: false, @@ -711,7 +711,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing }, 'gemini-1.5-flash-8b': { contextWindow: 1_048_576, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 0.0375, output: 0.15 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now downloadable: false, supportsFIM: false, @@ -733,14 +733,14 @@ const deepseekModelOptions = { 'deepseek-chat': { ...openSourceModelOptions_assumingOAICompat.deepseekR1, contextWindow: 64_000, // https://api-docs.deepseek.com/quick_start/pricing - maxOutputTokens: 8_000, // 8_000, + reservedOutputTokenSpace: 8_000, // 8_000, cost: { cache_read: .07, input: .27, output: 1.10, }, downloadable: false, }, 'deepseek-reasoner': { ...openSourceModelOptions_assumingOAICompat.deepseekCoderV2, contextWindow: 64_000, - maxOutputTokens: 8_000, // 8_000, + reservedOutputTokenSpace: 8_000, // 8_000, cost: { cache_read: .14, input: .55, output: 2.19, }, downloadable: false, }, @@ -763,7 +763,7 @@ const deepseekSettings: VoidStaticProviderInfo = { const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#pricing https://docs.mistral.ai/getting-started/models/models_overview/#premier-models 'mistral-large-latest': { contextWindow: 131_000, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 2.00, output: 6.00 }, supportsFIM: false, downloadable: { sizeGb: 73 }, @@ -772,7 +772,7 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici }, 'codestral-latest': { contextWindow: 256_000, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 0.30, output: 0.90 }, supportsFIM: true, downloadable: { sizeGb: 13 }, @@ -781,7 +781,7 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici }, 'ministral-8b-latest': { // ollama 'mistral' contextWindow: 131_000, - maxOutputTokens: 4_096, + reservedOutputTokenSpace: 4_096, cost: { input: 0.10, output: 0.10 }, supportsFIM: false, downloadable: { sizeGb: 4.1 }, @@ -790,7 +790,7 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici }, 'ministral-3b-latest': { contextWindow: 131_000, - maxOutputTokens: 4_096, + reservedOutputTokenSpace: 4_096, cost: { input: 0.04, output: 0.04 }, supportsFIM: false, downloadable: { sizeGb: 'not-known' }, @@ -809,7 +809,7 @@ const mistralSettings: VoidStaticProviderInfo = { const groqModelOptions = { // https://console.groq.com/docs/models, https://groq.com/pricing/ 'llama-3.3-70b-versatile': { contextWindow: 128_000, - maxOutputTokens: 32_768, // 32_768, + reservedOutputTokenSpace: 32_768, // 32_768, cost: { input: 0.59, output: 0.79 }, downloadable: false, supportsFIM: false, @@ -818,7 +818,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq }, 'llama-3.1-8b-instant': { contextWindow: 128_000, - maxOutputTokens: 8_192, + reservedOutputTokenSpace: 8_192, cost: { input: 0.05, output: 0.08 }, downloadable: false, supportsFIM: false, @@ -827,7 +827,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq }, 'qwen-2.5-coder-32b': { contextWindow: 128_000, - maxOutputTokens: null, // not specified? + reservedOutputTokenSpace: null, // not specified? cost: { input: 0.79, output: 0.79 }, downloadable: false, supportsFIM: false, // unfortunately looks like no FIM support on groq @@ -836,7 +836,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq }, 'qwen-qwq-32b': { // https://huggingface.co/Qwen/QwQ-32B contextWindow: 128_000, - maxOutputTokens: null, // not specified? + reservedOutputTokenSpace: null, // not specified? cost: { input: 0.29, output: 0.39 }, downloadable: false, supportsFIM: false, @@ -882,7 +882,7 @@ const microsoftAzureSettings: VoidStaticProviderInfo = { const ollamaModelOptions = { 'qwen2.5-coder:7b': { contextWindow: 32_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: { sizeGb: 1.9 }, supportsFIM: true, @@ -891,7 +891,7 @@ const ollamaModelOptions = { }, 'qwen2.5-coder:3b': { contextWindow: 32_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: { sizeGb: 1.9 }, supportsFIM: true, @@ -900,7 +900,7 @@ const ollamaModelOptions = { }, 'qwen2.5-coder:1.5b': { contextWindow: 32_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: { sizeGb: .986 }, supportsFIM: true, @@ -909,7 +909,7 @@ const ollamaModelOptions = { }, 'llama3.1': { contextWindow: 128_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: { sizeGb: 4.9 }, supportsFIM: false, @@ -918,7 +918,7 @@ const ollamaModelOptions = { }, 'qwen2.5-coder': { contextWindow: 128_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: { sizeGb: 4.7 }, supportsFIM: false, @@ -927,7 +927,7 @@ const ollamaModelOptions = { }, 'qwq': { contextWindow: 128_000, - maxOutputTokens: 32_000, + reservedOutputTokenSpace: 32_000, cost: { input: 0, output: 0 }, downloadable: { sizeGb: 20 }, supportsFIM: false, @@ -936,7 +936,7 @@ const ollamaModelOptions = { }, 'deepseek-r1': { contextWindow: 128_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: { sizeGb: 4.7 }, supportsFIM: false, @@ -986,7 +986,7 @@ const liteLLMSettings: VoidStaticProviderInfo = { // https://docs.litellm.ai/doc const openRouterModelOptions_assumingOpenAICompat = { 'mistralai/mistral-small-3.1-24b-instruct:free': { contextWindow: 128_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: false, supportsFIM: false, @@ -995,7 +995,7 @@ const openRouterModelOptions_assumingOpenAICompat = { }, 'google/gemini-2.0-flash-lite-preview-02-05:free': { contextWindow: 1_048_576, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: false, supportsFIM: false, @@ -1004,7 +1004,7 @@ const openRouterModelOptions_assumingOpenAICompat = { }, 'google/gemini-2.0-pro-exp-02-05:free': { contextWindow: 1_048_576, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: false, supportsFIM: false, @@ -1013,7 +1013,7 @@ const openRouterModelOptions_assumingOpenAICompat = { }, 'google/gemini-2.0-flash-exp:free': { contextWindow: 1_048_576, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0, output: 0 }, downloadable: false, supportsFIM: false, @@ -1023,13 +1023,13 @@ const openRouterModelOptions_assumingOpenAICompat = { 'deepseek/deepseek-r1': { ...openSourceModelOptions_assumingOAICompat.deepseekR1, contextWindow: 128_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0.8, output: 2.4 }, downloadable: false, }, 'anthropic/claude-3.7-sonnet:thinking': { contextWindow: 200_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 3.00, output: 15.00 }, downloadable: false, supportsFIM: false, @@ -1038,13 +1038,13 @@ const openRouterModelOptions_assumingOpenAICompat = { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, - reasoningMaxOutputTokens: 64_000, + reasoningReservedOutputTokenSpace: 64_000, reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 }, }, 'anthropic/claude-3.7-sonnet': { contextWindow: 200_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 3.00, output: 15.00 }, downloadable: false, supportsFIM: false, @@ -1053,7 +1053,7 @@ const openRouterModelOptions_assumingOpenAICompat = { }, 'anthropic/claude-3.5-sonnet': { contextWindow: 200_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 3.00, output: 15.00 }, downloadable: false, supportsFIM: false, @@ -1063,7 +1063,7 @@ const openRouterModelOptions_assumingOpenAICompat = { 'mistralai/codestral-2501': { ...openSourceModelOptions_assumingOAICompat.codestral, contextWindow: 256_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0.3, output: 0.9 }, downloadable: false, reasoningCapabilities: false, @@ -1071,14 +1071,14 @@ const openRouterModelOptions_assumingOpenAICompat = { 'qwen/qwen-2.5-coder-32b-instruct': { ...openSourceModelOptions_assumingOAICompat['qwen2.5coder'], contextWindow: 33_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0.07, output: 0.16 }, downloadable: false, }, 'qwen/qwq-32b': { ...openSourceModelOptions_assumingOAICompat['qwq'], contextWindow: 33_000, - maxOutputTokens: null, + reservedOutputTokenSpace: null, cost: { input: 0.07, output: 0.16 }, downloadable: false, } @@ -1201,12 +1201,12 @@ export const getIsReasoningEnabledState = ( } -export const getMaxOutputTokens = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean, overridesOfModel: OverridesOfModel | undefined }) => { +export const getReservedOutputTokenSpace = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean, overridesOfModel: OverridesOfModel | undefined }) => { const { reasoningCapabilities, - maxOutputTokens, + reservedOutputTokenSpace, } = getModelCapabilities(providerName, modelName, opts.overridesOfModel) - return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens + return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningReservedOutputTokenSpace : reservedOutputTokenSpace } // used to force reasoning state (complex) into something simple we can just read from when sending a message diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 9b6a51cd..4cad7d20 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -16,7 +16,7 @@ import { GoogleAuth } from 'google-auth-library' import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'; import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; -import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js'; +import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getReservedOutputTokenSpace } from '../../common/modelCapabilities.js'; import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js'; import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; @@ -430,7 +430,7 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} // anthropic-specific - max tokens - const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled, overridesOfModel }) + const maxTokens = getReservedOutputTokenSpace(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled, overridesOfModel }) // tools const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null From 732269de870a3bf05b6e3bc94080f7bb5e740ebf Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 5 May 2025 00:03:47 -0700 Subject: [PATCH 17/34] misc --- src/vs/workbench/contrib/void/browser/editCodeService.ts | 4 +++- .../void/browser/react/src/sidebar-tsx/SidebarChat.tsx | 4 ++-- .../browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 2cc7d942..bb189b2e 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1216,7 +1216,7 @@ class EditCodeService extends Disposable implements IEditCodeService { onFinishEdit() } - this._writeURIText(uri, newContent, 'wholeFileRange', { shouldRealignDiffAreas: false }) + this._writeURIText(uri, newContent, 'wholeFileRange', { shouldRealignDiffAreas: true }) onDone() } @@ -1947,6 +1947,8 @@ class EditCodeService extends Disposable implements IEditCodeService { if (blocks.length === 0) { this._notificationService.info(`Void: We ran Fast Apply, but the LLM didn't output any changes.`) } + this._writeURIText(uri, originalFileCode, 'wholeFileRange', { shouldRealignDiffAreas: true }) + try { this._instantlyApplySRBlocks(uri, fullText) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index a202973f..92b7d20e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -218,8 +218,8 @@ const nameOfChatMode = { const detailOfChatMode = { 'normal': 'Normal chat', - 'gather': 'Discover relevant files', - 'agent': 'Edit files and use tools', + 'gather': 'Reads files, but can\'t edit', + 'agent': 'Edits files and uses tools', } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 5e31c59b..e69c67ab 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -377,7 +377,7 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni
{/* spinner */} - {isRunning === 'LLM' || isRunning === 'tool' ? + {isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'idle' ? : isRunning === 'awaiting_user' ? : From 398a8a1934a5511924ffdb0a4742d49378ca687f Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 5 May 2025 00:50:33 -0700 Subject: [PATCH 18/34] misc + model overrides --- .voidrules | 4 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 1 + .../contrib/void/browser/sidebarActions.ts | 2 +- .../contrib/void/common/modelCapabilities.ts | 111 ++++++++++-------- .../void/common/voidSettingsService.ts | 20 ++-- .../contrib/void/common/voidSettingsTypes.ts | 4 +- .../llmMessage/sendLLMMessage.impl.ts | 2 +- 7 files changed, 77 insertions(+), 67 deletions(-) diff --git a/.voidrules b/.voidrules index 08826ef2..8cded830 100644 --- a/.voidrules +++ b/.voidrules @@ -3,4 +3,6 @@ This is a fork of the VSCode repo called Void. Most code we care about lives in src/vs/workbench/contrib/void. You may often need to explore the full repo to find relevant parts of code. -Look for services, and built-in functions that you might need to use to solve the problem. \ No newline at end of file +Look for services, and built-in functions that you might need to use to solve the problem. + +NEVER lazily cast to 'any' in typescript. Find the correct type to apply and use it. diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 92b7d20e..7f11ff15 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -728,6 +728,7 @@ const ToolHeaderWrapper = ({
{/* left */}
{ const commandService = accessor.get(ICommandService) await commandService.executeCommand(VOID_OPEN_SIDEBAR_ACTION_ID) - await commandService.executeCommand(VOID_ADD_SELECTION_TO_SIDEBAR_ACTION_ID) + // await commandService.executeCommand(VOID_ADD_SELECTION_TO_SIDEBAR_ACTION_ID) } }) diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 455b9e78..8668f830 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -140,42 +140,46 @@ export const defaultModelsOfProvider = { export type VoidStaticModelInfo = { // not stateful - contextWindow: number; // input tokens - reservedOutputTokenSpace: number | null; // output tokens, defaults to 4092 - cost: { // <-- UNUSED + cost: { // just informative, not used in sending / receiving input: number; output: number; cache_read?: number; cache_write?: number; } - downloadable: false | { + downloadable: false | { // just informative, not used in sending / receiving sizeGb: number | 'not-known' } + contextWindow: number; // input tokens + reservedOutputTokenSpace: number | null; // reserve this much space in the context window for output, defaults to 4092 if null + supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special paramete specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style', // null defaults to XML supportsFIM: boolean; + // reasoning options if supports reasoning reasoningCapabilities: false | { - readonly supportsReasoning: true; - // reasoning options if supports reasoning + readonly supportsReasoning: true; // this must be true for clarity readonly canTurnOffReasoning: boolean; // whether or not the user can disable reasoning mode (false if the model only supports reasoning) readonly canIOReasoning: boolean; // whether or not the model actually outputs reasoning (eg o1 lets us control reasoning but not output it) readonly reasoningReservedOutputTokenSpace?: number; // overrides normal reservedOutputTokenSpace - readonly reasoningBudgetSlider?: { type: 'slider'; min: number; max: number; default: number }; + readonly reasoningBudgetSlider?: + | undefined + | { type: 'number_slider'; min: number; max: number; default: number } // anthropic only supports this + | { type: 'string_slider'; values: string[]; default: string } // openai-compatible only supports this // options related specifically to model output - // you are allowed to not include openSourceThinkTags if it's not open source (no such cases as of writing) - // if it's open source, put the think tags here so we parse them out in e.g. ollama + // if it's open source, put the think tags here and we'll parse them out in e.g. ollama readonly openSourceThinkTags?: [string, string]; }; } -export type ModelOverrideOptions = Partial> +> + @@ -343,9 +347,12 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = ( const lower = modelName.toLowerCase() - const toFallback = (opts: Omit): VoidStaticModelInfo & { modelName: string } => { + const toFallback = },>(obj: T, recognizedModelName: string & keyof T) + : VoidStaticModelInfo & { modelName: string } => { + + const opts = obj[recognizedModelName] return { - modelName, + modelName: recognizedModelName, ...opts, supportsSystemMessage: opts.supportsSystemMessage ? 'system-role' : false, cost: { input: 0, output: 0 }, @@ -353,58 +360,58 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = ( ...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('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('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('grok')) return toFallback(xAIModelOptions, 'grok-2') - 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 }) - if (lower.includes('deepseek')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekCoderV3 }) + 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') + if (lower.includes('deepseek')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV3') - if (lower.includes('llama3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.llama3, }) - if (lower.includes('llama3.1')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama3.1'], }) - if (lower.includes('llama3.2')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama3.2'], }) - if (lower.includes('llama3.3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama3.3'], }) - if (lower.includes('llama') || lower.includes('scout')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama4-scout'] }) - if (lower.includes('llama') || lower.includes('maverick')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama4-scout'] }) - if (lower.includes('llama')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama4-scout'] }) + if (lower.includes('llama3')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3') + if (lower.includes('llama3.1')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.1') + if (lower.includes('llama3.2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.2') + if (lower.includes('llama3.3')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.3') + if (lower.includes('llama') || lower.includes('scout')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout') + if (lower.includes('llama') || lower.includes('maverick')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout') + if (lower.includes('llama')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout') - if (lower.includes('qwen') && lower.includes('2.5') && lower.includes('coder')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['qwen2.5coder'] }) - if (lower.includes('qwen') && lower.includes('3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['qwen3'] }) - if (lower.includes('qwen')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['qwen3'] }) - 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('qwen') && lower.includes('2.5') && lower.includes('coder')) return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen2.5coder') + if (lower.includes('qwen') && lower.includes('3')) return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen3') + if (lower.includes('qwen')) return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen3') + 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('gemma')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.gemma, }) + if (lower.includes('gemma')) return toFallback(openSourceModelOptions_assumingOAICompat, 'gemma') - if (lower.includes('starcoder2')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.starcoder2, }) + if (lower.includes('starcoder2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'starcoder2') - if (lower.includes('openhands')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['openhands-lm-32b'], }) // max output unclear + if (lower.includes('openhands')) return toFallback(openSourceModelOptions_assumingOAICompat, 'openhands-lm-32b') // max output uncler - if (lower.includes('quasar') || lower.includes('quaser')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['quasar'] }) + 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('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('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 (lower.includes('o4') && lower.includes('mini')) return toFallback(openAIModelOptions['o4-mini']) + 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 (lower.includes('o4') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o4-mini') if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower)) - return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat]) + return toFallback(openSourceModelOptions_assumingOAICompat, lower as keyof typeof openSourceModelOptions_assumingOAICompat) - return toFallback(defaultModelOptions) + return null } @@ -427,7 +434,7 @@ const anthropicModelOptions = { canTurnOffReasoning: true, canIOReasoning: true, reasoningReservedOutputTokenSpace: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19 - reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 + reasoningBudgetSlider: { type: 'number_slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 }, }, @@ -1039,7 +1046,7 @@ const openRouterModelOptions_assumingOpenAICompat = { canTurnOffReasoning: false, canIOReasoning: true, reasoningReservedOutputTokenSpace: 64_000, - reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 + reasoningBudgetSlider: { type: 'number_slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 }, }, 'anthropic/claude-3.7-sonnet': { @@ -1224,7 +1231,7 @@ export const getSendableReasoningInfo = ( if (!isReasoningEnabled) return null // check for reasoning budget - const reasoningBudget = reasoningBudgetSlider?.type === 'slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined + const reasoningBudget = reasoningBudgetSlider?.type === 'number_slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined if (reasoningBudget) { return { type: 'budgetEnabled', isReasoningEnabled: isReasoningEnabled, reasoningBudget: reasoningBudget } } diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index f8081b9c..91847fec 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -11,7 +11,7 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IMetricsService } from './metricsService.js'; -import { defaultProviderSettings, getModelCapabilities, ModelOverrideOptions } from './modelCapabilities.js'; +import { defaultProviderSettings, getModelCapabilities, ModelOverrides } from './modelCapabilities.js'; import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js'; import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel } from './voidSettingsTypes.js'; @@ -62,7 +62,9 @@ export interface IVoidSettingsService { setModelSelectionOfFeature: SetModelSelectionOfFeatureFn; setOptionsOfModelSelection: SetOptionsOfModelSelection; setGlobalSetting: SetGlobalSettingFn; - setOverridesOfModel(providerName: ProviderName, modelName: string, overrides: ModelOverrideOptions): Promise; + + // setting to undefined CLEARS it, unlike others: + setOverridesOfModel(providerName: ProviderName, modelName: string, overrides: Partial | undefined): Promise; dangerousSetState(newState: VoidSettingsState): Promise; resetState(): Promise; @@ -438,19 +440,17 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { this._onDidChangeState.fire() } - setOverridesOfModel = async (providerName: ProviderName, modelName: string, overrides: ModelOverrideOptions) => { - const currentProviderSettings = this.state.overridesOfModel[providerName] || {}; - + setOverridesOfModel = async (providerName: ProviderName, modelName: string, overrides: Partial | undefined) => { const newState: VoidSettingsState = { ...this.state, overridesOfModel: { ...this.state.overridesOfModel, [providerName]: { - ...currentProviderSettings, - [modelName]: { - ...currentProviderSettings[modelName], + ...this.state.overridesOfModel[providerName], + [modelName]: overrides === undefined ? undefined : { + ...this.state.overridesOfModel[providerName][modelName], ...overrides - } + }, } } }; @@ -459,7 +459,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { await this._storeState(); this._onDidChangeState.fire(); - this._metricsService.capture('Update Model Settings', { providerName, modelName, overrides }); + this._metricsService.capture('Update Model Overrides', { providerName, modelName, overrides }); } diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index 43fc9a24..187660dd 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -4,7 +4,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { defaultModelsOfProvider, defaultProviderSettings, ModelOverrideOptions } from './modelCapabilities.js'; +import { defaultModelsOfProvider, defaultProviderSettings, ModelOverrides } from './modelCapabilities.js'; import { ToolApprovalType } from './toolsServiceTypes.js'; import { VoidSettingsState } from './voidSettingsService.js' @@ -482,7 +482,7 @@ export type OptionsOfModelSelection = { export type OverridesOfModel = { [providerName in ProviderName]: { - [modelName: string]: ModelOverrideOptions | undefined + [modelName: string]: Partial | undefined } } diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 4cad7d20..feb18b1b 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -241,7 +241,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE const { providerReasoningIOSettings } = getProviderCapabilities(providerName) // reasoning - const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {} + const { canIOReasoning, openSourceThinkTags } = reasoningCapabilities || {} const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} From 7846b117bd52f5209aa79dc9689a243f120773fd Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 5 May 2025 00:52:56 -0700 Subject: [PATCH 19/34] model settings overrides part ii --- .../react/src/sidebar-tsx/SidebarChat.tsx | 63 +- .../src/void-onboarding/VoidOnboarding.tsx | 2 +- .../react/src/void-settings-tsx/Settings.tsx | 612 ++++++------------ .../contrib/void/browser/toolsService.ts | 1 - .../contrib/void/common/modelCapabilities.ts | 103 +-- .../contrib/void/common/voidSettingsTypes.ts | 1 + 6 files changed, 312 insertions(+), 470 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 7f11ff15..592ad006 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -159,23 +159,26 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => const { modelName, providerName } = modelSelection const { reasoningCapabilities } = getModelCapabilities(providerName, modelName, overridesOfModel) - const { canTurnOffReasoning, reasoningBudgetSlider } = reasoningCapabilities || {} + const { canTurnOffReasoning, reasoningSlider: reasoningBudgetSlider } = reasoningCapabilities || {} const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[modelName] const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) - if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now) - return null // unused right now - // return
- // {isReasoningEnabled ? 'Thinking' : 'Thinking'} - // { } } - // /> - //
+ + if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider + return
+ Thinking + { + const isOff = canTurnOffReasoning && !newVal + voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff }) + }} + /> +
} - if (reasoningBudgetSlider?.type === 'slider') { // if it's a slider + if (reasoningBudgetSlider?.type === 'budget_slider') { // if it's a slider const { min: min_, max, default: defaultVal } = reasoningBudgetSlider const nSteps = 8 // only used in calculating stepSize, stepSize is what actually matters @@ -186,7 +189,6 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => const value = isReasoningEnabled ? voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningBudget ?? defaultVal : valueIfOff - return
Thinking step={stepSize} value={value} onChange={(newVal) => { - const disabled = newVal === min && canTurnOffReasoning - voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !disabled, reasoningBudget: newVal }) + const isOff = canTurnOffReasoning && newVal === min + voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningBudget: newVal }) }} /> {isReasoningEnabled ? `${value} tokens` : 'Thinking disabled'}
} + if (reasoningBudgetSlider?.type === 'effort_slider') { + + const { values, default: defaultVal } = reasoningBudgetSlider + + const min = canTurnOffReasoning ? -1 : 0 + const max = values.length - 1 + + const valueIfOff = -1 + + const value = isReasoningEnabled ? + values.indexOf(voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningEffort ?? defaultVal) + : valueIfOff + + return
+ Thinking + { + const isOff = canTurnOffReasoning && newVal === min + voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningBudget: newVal }) + }} + /> + {isReasoningEnabled ? `${value}` : 'Thinking disabled'} +
+ } + return null } diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx index bf8a3223..8c968c62 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -355,7 +355,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName const { showAsDefault, isDownloaded } = infoOfModelName[modelName] ?? {} - const capabilities = getModelCapabilities(providerName, modelName) + const capabilities = getModelCapabilities(providerName, modelName, undefined) const { downloadable, cost, diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 20a4e9d0..e1e644ce 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -3,7 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react' +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; // Added useRef import just in case it was missed, though likely already present import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js' import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js' @@ -18,6 +18,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'; const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => { @@ -183,6 +184,197 @@ const ConfirmButton = ({ children, onConfirm, className }: { children: React.Rea ); }; +// ---------------- Simplified Model Settings Dialog ------------------ +// This new dialog replaces the verbose UI with a single JSON override box. +const SimpleModelSettingsDialog = ({ + isOpen, + onClose, + modelInfo, +}: { + isOpen: boolean; + onClose: () => void; + modelInfo: { modelName: string; providerName: ProviderName; type: 'autodetected' | 'custom' | 'default' } | null; +}) => { + if (!isOpen || !modelInfo) return null; + + const { modelName, providerName, type } = modelInfo; + const accessor = useAccessor(); + const settingsState = useSettingsState(); + const mouseDownInsideModal = useRef(false); // Ref to track mousedown origin + const settingsStateService = accessor.get('IVoidSettingsService'); + + // current overrides and defaults + const defaultModelCapabilities = getModelCapabilities(providerName, modelName, undefined); + const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] ?? undefined; + const { modelName: 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 = {}; + for (const k of allowedKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; } + const placeholder = JSON.stringify(partialDefaults, null, 2); + + const [overrideEnabled, setOverrideEnabled] = useState(() => !!currentOverrides); + const [jsonText, setJsonText] = useState(() => currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder); + + const [readOnlyHeight, setReadOnlyHeight] = useState(undefined); + const [errorMsg, setErrorMsg] = useState(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); + onClose(); + return; + } + + // enabled overrides + // parse json + let parsedInput: Record + if (jsonText.trim()) { + try { + parsedInput = JSON.parse(jsonText); + } catch (e) { + setErrorMsg('Invalid JSON'); + return; + } + } else { + setErrorMsg('Invalid JSON'); + return; + } + + // only keep allowed keys + const cleaned: Partial = {}; + for (const k of allowedKeys) { + if (!(k in parsedInput)) continue + const isEmpty = parsedInput[k] === '' || parsedInput[k] === null || parsedInput[k] === undefined; + if (!isEmpty && (k in partialDefaults)) { + cleaned[k] = parsedInput[k] as any; + } + } + await settingsStateService.setOverridesOfModel(providerName, modelName, cleaned); + onClose(); + }; + + return ( +
{ + mouseDownInsideModal.current = false; + }} + onMouseUp={() => { + if (!mouseDownInsideModal.current) { + onClose(); + } + mouseDownInsideModal.current = false; + }} + > + {/* MODAL */} +
e.stopPropagation()} // Keep stopping propagation for normal clicks inside + onMouseDown={(e) => { + mouseDownInsideModal.current = true; + e.stopPropagation(); + }} + > +
+

+ Change Defaults for {modelName} ({displayInfoOfProviderName(providerName).title}) +

+ +
+ + {/* Display model recognition status */} +
+ {type === 'default' ? `${modelName} comes packaged with Void, so you shouldn't need to change these settings.` + : isUnrecognizedModel + ? `Model not recognized by Void.` + : `Void knows about this model ("${recognizedModelName}")!`} +
+ + + {/* override toggle */} +
+ + Override Model Defaults +
+ + + + {overrideEnabled ? ( + + ) : ( +