mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Merge pull request #451 from voideditor/model-selection
Azure, Reasoning, Model overrides, Fix Search/Replace without whitespace, File dump, New models
This commit is contained in:
commit
f97cdd82d8
31 changed files with 1334 additions and 471 deletions
|
|
@ -2,4 +2,7 @@ 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.
|
||||
|
||||
NEVER lazily cast to 'any' in typescript. Find the correct type to apply and use it.
|
||||
|
|
|
|||
159
package-lock.json
generated
159
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"nameShort": "Void",
|
||||
"nameLong": "Void",
|
||||
"voidVersion": "1.2.8",
|
||||
"voidVersion": "1.3.0",
|
||||
"applicationName": "void",
|
||||
"dataFolderName": ".void-editor",
|
||||
"win32MutexName": "voideditor",
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
"builtInExtensions": [],
|
||||
"linkProtectionTrustedDomains": [
|
||||
"https://voideditor.com",
|
||||
"https://voideditor.dev"
|
||||
"https://voideditor.dev",
|
||||
"https://github.com/voideditor/void"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -790,6 +790,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
console.log('starting autocomplete...', predictionType)
|
||||
|
||||
const featureName: FeatureName = 'Autocomplete'
|
||||
const overridesOfModel = this._settingsService.state.overridesOfModel
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
|
@ -807,6 +808,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
}),
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
logging: { loggingName: 'Autocomplete' },
|
||||
onText: () => { }, // unused in FIMMessage
|
||||
// onText: async ({ fullText, newText }) => {
|
||||
|
|
|
|||
|
|
@ -641,6 +641,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// above just defines helpers, below starts the actual function
|
||||
const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here
|
||||
const { overridesOfModel } = this._settingsService.state
|
||||
|
||||
let nMessagesSent = 0
|
||||
let shouldSendAnotherMessage = true
|
||||
|
|
@ -682,8 +683,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 })
|
||||
|
|
@ -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 }) => {
|
||||
|
|
@ -724,7 +726,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
const llmRes = await messageIsDonePromise // wait for message to complete
|
||||
if (this.streamState[threadId]?.isRunning !== 'LLM') {
|
||||
console.log('Unexpected chat agent state when', this.streamState[threadId]?.isRunning)
|
||||
this._setStreamState(threadId, undefined)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -737,7 +738,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)
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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,14 +252,14 @@ export type GeminiMessage = {
|
|||
// --- CHAT ---
|
||||
|
||||
const prepareOpenAIOrAnthropicMessages = ({
|
||||
messages,
|
||||
messages: messages_,
|
||||
systemMessage,
|
||||
aiInstructions,
|
||||
supportsSystemMessage,
|
||||
specialToolFormat,
|
||||
supportsAnthropicReasoning,
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
reservedOutputTokenSpace,
|
||||
}: {
|
||||
messages: SimpleLLMMessage[],
|
||||
systemMessage: string,
|
||||
|
|
@ -268,20 +268,32 @@ 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 ================
|
||||
// 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<number>()
|
||||
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 - 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)
|
||||
)
|
||||
|
||||
|
||||
// <----------------------------------------->
|
||||
// 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: `<SYSTEM_MESSAGE>\n${newSystemMessage}\n</SYSTEM_MESSAGE>\n${llmMessages[0].content}`
|
||||
content: `<SYSTEM_MESSAGE>\n${newSysMsg}\n</SYSTEM_MESSAGE>\n${llmMessages[0].content}`
|
||||
} as const
|
||||
llmMessages.splice(0, 1) // delete first message
|
||||
llmMessages.unshift(newFirstMessage) // add new first message
|
||||
|
|
@ -470,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 } => {
|
||||
|
||||
|
|
@ -607,20 +631,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 reservedOutputTokenSpace = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled, overridesOfModel })
|
||||
|
||||
const { messages, separateSystemMessage } = prepareMessages({
|
||||
messages: simpleMessages,
|
||||
|
|
@ -630,19 +657,22 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
specialToolFormat,
|
||||
supportsAnthropicReasoning: providerName === 'anthropic',
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
reservedOutputTokenSpace,
|
||||
providerName,
|
||||
})
|
||||
return { messages, separateSystemMessage };
|
||||
}
|
||||
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 +680,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 reservedOutputTokenSpace = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled, overridesOfModel })
|
||||
const llmMessages = this._chatMessagesToSimpleMessages(chatMessages)
|
||||
|
||||
const { messages, separateSystemMessage } = prepareMessages({
|
||||
|
|
@ -662,7 +692,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
specialToolFormat,
|
||||
supportsAnthropicReasoning: providerName === 'anthropic',
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
reservedOutputTokenSpace,
|
||||
providerName,
|
||||
})
|
||||
return { messages, separateSystemMessage };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ 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 { 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';
|
||||
|
|
@ -106,37 +109,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)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -575,7 +583,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 })
|
||||
|
|
@ -1208,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()
|
||||
}
|
||||
|
||||
|
|
@ -1331,6 +1339,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 +1491,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
separateSystemMessage,
|
||||
chatMode: null, // not chat
|
||||
onText: (params) => {
|
||||
|
|
@ -1556,17 +1566,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, 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 +1601,31 @@ 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 origStart = (startLine !== 0 ?
|
||||
modelStrLines.slice(0, startLine).join('\n') + '\n'
|
||||
: '').length
|
||||
|
||||
// including endline at end
|
||||
const origEnd = modelStrLines.slice(0, endLine + 1).join('\n').length - 1
|
||||
|
||||
replacements.push({ origStart, origEnd, block: b });
|
||||
}
|
||||
|
||||
// sort in increasing order
|
||||
replacements.sort((a, b) => a.origStart - b.origStart)
|
||||
|
||||
|
|
@ -1615,12 +1647,12 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
'wholeFileRange',
|
||||
{ shouldRealignDiffAreas: true }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise<void>] | undefined {
|
||||
const { from, applyStr, } = opts
|
||||
const featureName: FeatureName = 'Apply'
|
||||
const overridesOfModel = this._settingsService.state.overridesOfModel
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
|
@ -1900,6 +1932,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
separateSystemMessage,
|
||||
chatMode: null, // not chat
|
||||
onText: (params) => {
|
||||
|
|
@ -1913,6 +1946,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)
|
||||
|
|
@ -2220,31 +2255,81 @@ registerSingleton(IEditCodeService, EditCodeService, InstantiationType.Eager);
|
|||
|
||||
|
||||
|
||||
const processRawKeybindingText = (keybindingStr: string) => {
|
||||
return keybindingStr
|
||||
.replace(/Enter/g, '↵') // ⏎
|
||||
.replace(/Backspace/g, '⌫')
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService
|
||||
) {
|
||||
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);
|
||||
|
||||
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', []),
|
||||
|
|
@ -2258,11 +2343,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;
|
||||
|
|
@ -2276,10 +2364,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;
|
||||
|
|
@ -2293,6 +2383,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';
|
||||
|
||||
|
||||
|
||||
|
|
@ -2313,16 +2404,28 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget {
|
|||
}
|
||||
|
||||
// Mount first, then update positions
|
||||
editor.addOverlayWidget(this);
|
||||
|
||||
|
||||
updateTop()
|
||||
updateLeft()
|
||||
setTimeout(() => {
|
||||
updateTop()
|
||||
updateLeft()
|
||||
}, 0)
|
||||
|
||||
this._register(editor.onDidScrollChange(e => { updateTop() }))
|
||||
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 { acceptText, rejectText } = getAcceptRejectText()
|
||||
|
||||
acceptButton.textContent = acceptText;
|
||||
rejectButton.textContent = rejectText;
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
// mount this widget
|
||||
|
||||
editor.addOverlayWidget(this);
|
||||
|
|
@ -2330,8 +2433,8 @@ class AcceptRejectInlineWidget extends Widget implements IOverlayWidget {
|
|||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this.editor.removeOverlayWidget(this)
|
||||
super.dispose()
|
||||
this.editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ export interface IEditCodeService {
|
|||
diffOfId: Record<string, Diff>;
|
||||
|
||||
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 }>;
|
||||
|
|
|
|||
|
|
@ -325,13 +325,13 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string
|
|||
|
||||
export const BlockCodeApplyWrapper = ({
|
||||
children,
|
||||
initValue,
|
||||
codeStr,
|
||||
applyBoxId,
|
||||
language,
|
||||
canApply,
|
||||
uri,
|
||||
}: {
|
||||
initValue: string;
|
||||
codeStr: string;
|
||||
children: React.ReactNode;
|
||||
applyBoxId: string;
|
||||
canApply: boolean;
|
||||
|
|
@ -364,8 +364,8 @@ export const BlockCodeApplyWrapper = ({
|
|||
</div>
|
||||
<div className={`${canApply ? '' : 'hidden'} flex items-center gap-1`}>
|
||||
<JumpToFileButton uri={uri} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={initValue} toolTipName='Copy' />}
|
||||
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={initValue} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} toolTipName='Copy' />}
|
||||
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={codeStr} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -319,12 +319,12 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
|||
return <BlockCodeApplyWrapper
|
||||
canApply={isCodeblockClosed}
|
||||
applyBoxId={applyBoxId}
|
||||
initValue={contents}
|
||||
codeStr={contents}
|
||||
language={language}
|
||||
uri={uri || 'current'}
|
||||
>
|
||||
<BlockCode
|
||||
initValue={contents}
|
||||
initValue={contents.trimEnd()} // \n\n adds a permanent newline which creates a flash
|
||||
language={language}
|
||||
/>
|
||||
</BlockCodeApplyWrapper>
|
||||
|
|
|
|||
|
|
@ -153,27 +153,32 @@ 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 { canTurnOffReasoning, reasoningBudgetSlider } = reasoningCapabilities || {}
|
||||
const { reasoningCapabilities } = getModelCapabilities(providerName, modelName, overridesOfModel)
|
||||
const { canTurnOffReasoning, reasoningSlider: reasoningBudgetSlider } = reasoningCapabilities || {}
|
||||
|
||||
const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[modelName]
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
|
||||
if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now)
|
||||
return null // unused right now
|
||||
// return <div className='flex items-center gap-x-2'>
|
||||
// <span className='text-void-fg-3 text-xs pointer-events-none inline-block w-10'>{isReasoningEnabled ? 'Thinking' : 'Thinking'}</span>
|
||||
// <VoidSwitch
|
||||
// size='xs'
|
||||
// value={isReasoningEnabled}
|
||||
// onChange={(newVal) => { } }
|
||||
// />
|
||||
// </div>
|
||||
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel)
|
||||
|
||||
if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider
|
||||
return <div className='flex items-center gap-x-2'>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none inline-block w-10 pr-1'>Thinking</span>
|
||||
<VoidSwitch
|
||||
size='xxs'
|
||||
value={isReasoningEnabled}
|
||||
onChange={(newVal) => {
|
||||
const isOff = canTurnOffReasoning && !newVal
|
||||
voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -184,7 +189,6 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) =>
|
|||
const value = isReasoningEnabled ? voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningBudget ?? defaultVal
|
||||
: valueIfOff
|
||||
|
||||
|
||||
return <div className='flex items-center gap-x-2'>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none inline-block w-10 pr-1'>Thinking</span>
|
||||
<VoidSlider
|
||||
|
|
@ -195,14 +199,45 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) =>
|
|||
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 === valueIfOff
|
||||
voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningBudget: newVal })
|
||||
}}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{isReasoningEnabled ? `${value} tokens` : 'Thinking disabled'}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (reasoningBudgetSlider?.type === 'effort_slider') {
|
||||
|
||||
const { values, default: defaultVal } = reasoningBudgetSlider
|
||||
|
||||
const min = canTurnOffReasoning ? -1 : 0
|
||||
const max = values.length - 1
|
||||
|
||||
const currentEffort = voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningEffort ?? defaultVal
|
||||
const valueIfOff = -1
|
||||
const value = isReasoningEnabled && currentEffort ? values.indexOf(currentEffort) : valueIfOff
|
||||
|
||||
const currentEffortCapitalized = currentEffort.charAt(0).toUpperCase() + currentEffort.slice(1, Infinity)
|
||||
|
||||
return <div className='flex items-center gap-x-2'>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none inline-block w-10 pr-1'>Thinking</span>
|
||||
<VoidSlider
|
||||
width={30}
|
||||
size='xs'
|
||||
min={min}
|
||||
max={max}
|
||||
step={1}
|
||||
value={value}
|
||||
onChange={(newVal) => {
|
||||
const isOff = canTurnOffReasoning && newVal === valueIfOff
|
||||
voidSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningEffort: values[newVal] ?? undefined })
|
||||
}}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{isReasoningEnabled ? `${currentEffortCapitalized}` : 'Thinking disabled'}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -216,8 +251,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',
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -726,6 +761,7 @@ const ToolHeaderWrapper = ({
|
|||
<div className={`flex items-center w-full gap-x-2 overflow-hidden justify-between ${isRejected ? 'line-through' : ''}`}>
|
||||
{/* left */}
|
||||
<div className={`
|
||||
ml-1
|
||||
flex items-center min-w-0 overflow-hidden grow
|
||||
${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''}
|
||||
`}
|
||||
|
|
@ -2764,7 +2800,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) -----
|
||||
|
||||
|
|
@ -2913,11 +2949,9 @@ export const SidebarChat = () => {
|
|||
}
|
||||
}, [onSubmit, onAbort, isRunning])
|
||||
|
||||
|
||||
|
||||
const inputChatArea = <VoidChatArea
|
||||
featureName='Chat'
|
||||
onSubmit={onSubmit}
|
||||
onSubmit={() => onSubmit()}
|
||||
onAbort={onAbort}
|
||||
isStreaming={!!isRunning}
|
||||
isDisabled={isDisabled}
|
||||
|
|
@ -2986,14 +3020,14 @@ export const SidebarChat = () => {
|
|||
{landingPageInput}
|
||||
</ErrorBoundary>
|
||||
|
||||
{Object.keys(chatThreadsState.allThreads).length > 1 ? // show if there are threads
|
||||
{Object.keys(chatThreadsState.allThreads).length > 1 ? // show if there are threads
|
||||
<ErrorBoundary>
|
||||
<div className='pt-8 mb-2 text-void-fg-1 text-root'>Previous Threads</div>
|
||||
<div className='pt-8 mb-2 text-void-fg-3 text-root select-none pointer-events-none'>Previous Threads</div>
|
||||
<PastThreadsList />
|
||||
</ErrorBoundary>
|
||||
:
|
||||
<ErrorBoundary>
|
||||
<div className='pt-8 mb-2 text-void-fg-1 text-root'>Suggestions</div>
|
||||
<div className='pt-8 mb-2 text-void-fg-3 text-root select-none pointer-events-none'>Suggestions</div>
|
||||
{initiallySuggestedPromptsHTML}
|
||||
</ErrorBoundary>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni
|
|||
<div className="flex items-center justify-between gap-1">
|
||||
<span className="flex items-center gap-2 min-w-0 overflow-hidden">
|
||||
{/* spinner */}
|
||||
{isRunning === 'LLM' || isRunning === 'tool' ? <LoaderCircle className="animate-spin bg-void-stroke-1 flex-shrink-0 flex-grow-0" size={14} />
|
||||
{isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'idle' ? <LoaderCircle className="animate-spin bg-void-stroke-1 flex-shrink-0 flex-grow-0" size={14} />
|
||||
:
|
||||
isRunning === 'awaiting_user' ? <MessageCircleQuestion className="bg-void-stroke-1 flex-shrink-0 flex-grow-0" size={14} />
|
||||
:
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ export function getRelativeWorkspacePath(accessor: ReturnType<typeof useAccessor
|
|||
if (relativePath.startsWith('/')) {
|
||||
relativePath = relativePath.slice(1);
|
||||
}
|
||||
console.log({ folderPath, relativePath, uriPath });
|
||||
// console.log({ folderPath, relativePath, uriPath });
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
|
|
@ -383,9 +383,8 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
|
|||
// Focus the textarea first
|
||||
textarea.focus();
|
||||
|
||||
// The most reliable way to simulate typing is to use execCommand
|
||||
// which will trigger all the appropriate native events
|
||||
document.execCommand('insertText', false, text + ' '); // add space after too
|
||||
// Insert the @ to mention text in the editor (we decided not to do this for now)
|
||||
// document.execCommand('insertText', false, text + ' '); // add space after too
|
||||
|
||||
// React's onChange relies on a SyntheticEvent system
|
||||
// The best way to ensure it runs is to call callbacks directly
|
||||
|
|
@ -754,7 +753,7 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(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
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
const { model } = await voidModelService.getModelSafe(nextURI)
|
||||
if (model) {
|
||||
// switch to the URI
|
||||
editorService.openCodeEditor({ resource: model.uri, options: { revealIfVisible: true } }, editor)
|
||||
editorService.openCodeEditor({ resource: nextURI, options: { revealIfVisible: true } }, editor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -364,7 +364,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
|
|||
contextWindow,
|
||||
|
||||
isUnrecognizedModel,
|
||||
maxOutputTokens,
|
||||
reservedOutputTokenSpace,
|
||||
supportsSystemMessage,
|
||||
} = capabilities
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
* 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'
|
||||
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
|
||||
import { X, RefreshCw, Loader2, Check, Asterisk } from 'lucide-react'
|
||||
import { X, RefreshCw, Loader2, Check, Asterisk, Plus } from 'lucide-react'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { env } from '../../../../../../../base/common/process.js'
|
||||
import { ModelDropdown } from './ModelDropdown.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,180 @@ 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 { recognizedModelName, isUnrecognizedModel } = defaultModelCapabilities
|
||||
|
||||
// keys of ModelOverrides we allow the user to override
|
||||
const allowedKeys: (string & (keyof ModelOverrides))[] = [
|
||||
'contextWindow',
|
||||
'reservedOutputTokenSpace',
|
||||
'supportsSystemMessage',
|
||||
'specialToolFormat',
|
||||
'supportsFIM',
|
||||
'reasoningCapabilities',
|
||||
];
|
||||
|
||||
// Create the placeholder with the default values for allowed keys
|
||||
const partialDefaults: Partial<ModelOverrides> = {};
|
||||
for (const k of allowedKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; }
|
||||
const placeholder = JSON.stringify(partialDefaults, null, 2);
|
||||
|
||||
const [overrideEnabled, setOverrideEnabled] = useState<boolean>(() => !!currentOverrides);
|
||||
const [jsonText, setJsonText] = useState<string>(() => currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder);
|
||||
|
||||
const [readOnlyHeight, setReadOnlyHeight] = useState<number | undefined>(undefined);
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
|
||||
// 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<string, unknown>
|
||||
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<ModelOverrides> = {};
|
||||
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 (
|
||||
<div // Backdrop
|
||||
className="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999999]"
|
||||
onMouseDown={() => {
|
||||
mouseDownInsideModal.current = false;
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
if (!mouseDownInsideModal.current) {
|
||||
onClose();
|
||||
}
|
||||
mouseDownInsideModal.current = false;
|
||||
}}
|
||||
>
|
||||
{/* MODAL */}
|
||||
<div
|
||||
className="bg-void-bg-1 rounded-md p-4 max-w-xl w-full shadow-xl overflow-y-auto max-h-[90vh]"
|
||||
onClick={(e) => e.stopPropagation()} // Keep stopping propagation for normal clicks inside
|
||||
onMouseDown={(e) => {
|
||||
mouseDownInsideModal.current = true;
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-medium">
|
||||
Change Defaults for {modelName} ({displayInfoOfProviderName(providerName).title})
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-void-fg-3 hover:text-void-fg-1"
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Display model recognition status */}
|
||||
<div className="text-sm text-void-fg-3 mb-4">
|
||||
{type === 'default' ? `${modelName} comes packaged with Void, so you shouldn't need to change these settings.`
|
||||
: isUnrecognizedModel
|
||||
? `Model not recognized by Void.`
|
||||
: `Void recognizes ${modelName} ("${recognizedModelName}").`}
|
||||
</div>
|
||||
|
||||
|
||||
{/* override toggle */}
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<VoidSwitch size='xs' value={overrideEnabled} onChange={setOverrideEnabled} />
|
||||
<span className="text-void-fg-3 text-sm">Override model defaults</span>
|
||||
</div>
|
||||
|
||||
{/* Informational link */}
|
||||
{overrideEnabled && <div className="text-sm text-void-fg-3 mb-4">
|
||||
<ChatMarkdownRender string={"See the [sourcecode](https://github.com/voideditor/void/blob/d125d8698bf6ccd46c9367c1445e4adfe9aa2c1c/src/vs/workbench/contrib/void/common/modelCapabilities.ts#L144C1-L168C1) for a reference on how to set this JSON (advanced)."} chatMessageLocation={undefined} />
|
||||
</div>}
|
||||
|
||||
<textarea
|
||||
className={`w-full min-h-[200px] p-2 rounded-sm border border-void-border-2 bg-void-bg-2 resize-none font-mono text-sm ${!overrideEnabled ? 'text-void-fg-3' : ''}`}
|
||||
value={overrideEnabled ? jsonText : placeholder}
|
||||
placeholder={placeholder}
|
||||
onChange={overrideEnabled ? (e) => setJsonText(e.target.value) : undefined}
|
||||
readOnly={!overrideEnabled}
|
||||
/>
|
||||
{errorMsg && (
|
||||
<div className="text-red-500 mt-2 text-sm">{errorMsg}</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<VoidButtonBgDarken onClick={onClose} className="px-3 py-1">
|
||||
Cancel
|
||||
</VoidButtonBgDarken>
|
||||
<VoidButtonBgDarken
|
||||
onClick={onSave}
|
||||
className="px-3 py-1 bg-[#0e70c0] text-white"
|
||||
>
|
||||
Save
|
||||
</VoidButtonBgDarken>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// shows a providerName dropdown if no `providerName` is given
|
||||
export const AddModelInputBox = ({ providerName: permanentProviderName, className, compact }: { providerName?: ProviderName, className?: string, compact?: boolean }) => {
|
||||
|
|
@ -302,13 +477,20 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam
|
|||
}
|
||||
|
||||
|
||||
export const ModelDump = () => {
|
||||
|
||||
|
||||
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: 'autodetected' | 'custom' | 'default'
|
||||
} | null>(null);
|
||||
|
||||
// a dump of all the enabled providers' models
|
||||
const modelDump: (VoidStatefulModelInfo & { providerName: ProviderName, providerEnabled: boolean })[] = []
|
||||
for (let providerName of providerNames) {
|
||||
|
|
@ -335,41 +517,55 @@ export const ModelDump = () => {
|
|||
|
||||
const tooltipName = (
|
||||
disabled ? `Add ${providerTitle} to enable`
|
||||
: value === true ? 'Enabled'
|
||||
: 'Disabled'
|
||||
: value === true ? 'Show in Dropdown'
|
||||
: 'Hide from Dropdown'
|
||||
)
|
||||
|
||||
|
||||
const detailAboutModel = type === 'autodetected' ?
|
||||
<Asterisk size={14} className="inline-block align-text-top brightness-115 stroke-[2] text-[#0e70c0]" data-tooltip-id='void-tooltip' data-tooltip-place='right' data-tooltip-content='Detected locally' />
|
||||
: type === 'default' ? undefined
|
||||
: <Asterisk size={14} className="inline-block align-text-top brightness-115 stroke-[2] text-[#0e70c0]" data-tooltip-id='void-tooltip' data-tooltip-place='right' data-tooltip-content='Custom model' />
|
||||
: type === 'custom' ?
|
||||
<Asterisk size={14} className="inline-block align-text-top brightness-115 stroke-[2] text-[#0e70c0]" data-tooltip-id='void-tooltip' data-tooltip-place='right' data-tooltip-content='Custom model' />
|
||||
: undefined
|
||||
|
||||
const hasOverrides = !!settingsState.overridesOfModel?.[providerName]?.[modelName]
|
||||
|
||||
return <div key={`${modelName}${providerName}`}
|
||||
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate
|
||||
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate group
|
||||
`}
|
||||
>
|
||||
{/* left part is width:full */}
|
||||
<div className={`flex-grow flex items-center gap-4`}>
|
||||
<div className={`flex flex-grow items-center gap-4`}>
|
||||
<span className='w-full max-w-32'>{isNewProviderName ? providerTitle : ''}</span>
|
||||
<span className='w-fit truncate'>{modelName}{detailAboutModel}</span>
|
||||
<span className='w-fit truncate'>{modelName}</span>
|
||||
</div>
|
||||
|
||||
{/* right part is anything that fits */}
|
||||
<div className='flex items-center gap-4'
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-place='top'
|
||||
// data-tooltip-content={disabled ? `${displayInfoOfProviderName(providerName).title} is disabled`
|
||||
// : (isHidden ? `'${modelName}' won't appear in dropdowns` : ``)
|
||||
// }
|
||||
>
|
||||
<div className="flex items-center gap-2 w-fit">
|
||||
|
||||
{/* Advanced Settings button (gear). Hide entirely when provider/model disabled. */}
|
||||
{disabled ? null : (
|
||||
<div className="w-5 flex items-center justify-center">
|
||||
<button
|
||||
onClick={() => { setOpenSettingsModel({ modelName, providerName, type }) }}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='right'
|
||||
data-tooltip-content='Advanced Settings'
|
||||
className={`${hasOverrides ? '' : 'opacity-0 group-hover:opacity-100'} transition-opacity`}
|
||||
>
|
||||
<Plus size={12} className="text-void-fg-3 opacity-50" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Blue star */}
|
||||
{detailAboutModel}
|
||||
|
||||
|
||||
{/* <span className='opacity-50 truncate'>{type === 'autodetected' ? '(detected locally)' : type === 'default' ? '' : '(custom model)'}</span> */}
|
||||
|
||||
{/* Switch */}
|
||||
<VoidSwitch
|
||||
value={value}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName); }}
|
||||
disabled={disabled}
|
||||
size='sm'
|
||||
|
||||
|
|
@ -378,12 +574,20 @@ export const ModelDump = () => {
|
|||
data-tooltip-content={tooltipName}
|
||||
/>
|
||||
|
||||
{/* X button */}
|
||||
<div className={`w-5 flex items-center justify-center`}>
|
||||
{type === 'default' || type === 'autodetected' ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName) }}><X className='size-4' /></button>}
|
||||
{type === 'default' || type === 'autodetected' ? null : <button onClick={() => { settingsStateService.deleteModel(providerName, modelName); }}><X className="size-4" /></button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
|
||||
{/* Model Settings Dialog */}
|
||||
<SimpleModelSettingsDialog
|
||||
isOpen={openSettingsModel !== null}
|
||||
onClose={() => setOpenSettingsModel(null)}
|
||||
modelInfo={openSettingsModel}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
@ -514,8 +718,8 @@ export const SettingsForProvider = ({ providerName, showProviderTitle, showProvi
|
|||
|
||||
{showProviderSuggestions && needsModel ?
|
||||
providerName === 'ollama' ?
|
||||
<WarningBox text={`Please install an Ollama model. We'll auto-detect it.`} />
|
||||
: <WarningBox text={`Please add a model for ${providerTitle} (Models section).`} />
|
||||
<WarningBox className="mt-1" text={`Please install an Ollama model. We'll auto-detect it.`} />
|
||||
: <WarningBox className="mt-1" text={`Please add a model for ${providerTitle} (Models section).`} />
|
||||
: null}
|
||||
</div>
|
||||
</div >
|
||||
|
|
@ -1006,21 +1210,20 @@ export const Settings = () => {
|
|||
|
||||
|
||||
<h2 className={`text-3xl mt-12`}>Feature Options</h2>
|
||||
{/* L1 */}
|
||||
|
||||
<div className='flex items-start justify-around my-4 gap-x-8'>
|
||||
<div className='flex flex-col gap-y-8 my-4'>
|
||||
<ErrorBoundary>
|
||||
{/* FIM */}
|
||||
<div className='w-full'>
|
||||
<div>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>
|
||||
<span>
|
||||
Experimental.{' '}
|
||||
</span>
|
||||
<span
|
||||
className='hover:brightness-110'
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
|
||||
data-tooltip-content='We recommend using the largest qwen2.5-coder model you can with Ollama (try qwen2.5-coder:3b).'
|
||||
data-tooltip-class-name='void-max-w-[20px]'
|
||||
>
|
||||
Only works with FIM models.*
|
||||
|
|
@ -1057,7 +1260,7 @@ export const Settings = () => {
|
|||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Settings that control the behavior of the Apply button.</div>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>Settings that control the behavior of the Apply button.</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Sync to Chat Switch */}
|
||||
|
|
@ -1087,18 +1290,14 @@ export const Settings = () => {
|
|||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* L2 */}
|
||||
|
||||
<div className='flex items-start justify-around my-4 gap-x-8'>
|
||||
|
||||
{/* Tools Section */}
|
||||
<div className='w-full'>
|
||||
<div>
|
||||
<h4 className={`text-base`}>Tools</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>{`Tools are functions that LLMs can call. Some tools require user approval.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
|
|
@ -1130,7 +1329,7 @@ export const Settings = () => {
|
|||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>Editor</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>{`Settings that control the visibility of Void suggestions in the code editor.`}</div>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1'>{`Settings that control the visibility of Void suggestions in the code editor.`}</div>
|
||||
|
||||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
|
|
@ -1153,7 +1352,7 @@ export const Settings = () => {
|
|||
<div className='mt-12'>
|
||||
<ErrorBoundary>
|
||||
<h2 className='text-3xl mb-2 mt-12'>One-Click Switch</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer your settings from another editor to Void in one click.`}</h4>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer your editor settings into Void.`}</h4>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<OneClickSwitchButton className='w-48' fromEditor="VS Code" />
|
||||
|
|
@ -1166,6 +1365,7 @@ export const Settings = () => {
|
|||
{/* Import/Export section, as its own block right after One-Click Switch */}
|
||||
<div className='mt-12'>
|
||||
<h2 className='text-3xl mb-2'>Import/Export</h2>
|
||||
<h4 className='text-void-fg-3 mb-4'>{`Transfer Void's settings and chats in and out of Void.`}</h4>
|
||||
<div className='flex flex-col gap-8'>
|
||||
{/* Settings Subcategory */}
|
||||
<div className='flex flex-col gap-2 max-w-48 w-full'>
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ registerAction2(class extends Action2 {
|
|||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ const validateStr = (argName: string, value: unknown) => {
|
|||
|
||||
|
||||
// We are NOT checking to make sure in workspace
|
||||
// TODO!!!! check to make sure folder/file exists
|
||||
const validateURI = (uriStr: unknown) => {
|
||||
if (uriStr === null) throw new Error(`Invalid LLM output: uri was null.`)
|
||||
if (typeof uriStr !== 'string') throw new Error(`Invalid LLM output format: Provided uri must be a string, but it's a(n) ${typeof uriStr}. Full value: ${JSON.stringify(uriStr)}.`)
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
||||
|
||||
|
|
@ -164,10 +172,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 +194,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 +325,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 = {
|
||||
|
|
@ -390,8 +417,8 @@ class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget {
|
|||
|
||||
// Style the container
|
||||
// root.style.backgroundColor = 'rgb(248 113 113)';
|
||||
root.style.height = '16rem'; // make a fixed size, and all contents go on the bottom right. this fixes annoying VS Code mounting issues
|
||||
root.style.width = '16rem';
|
||||
root.style.height = '256px'; // make a fixed size, and all contents go on the bottom right. this fixes annoying VS Code mounting issues
|
||||
root.style.width = '100%';
|
||||
root.style.flexDirection = 'column';
|
||||
root.style.justifyContent = 'flex-end';
|
||||
root.style.alignItems = 'flex-end';
|
||||
|
|
@ -439,3 +466,74 @@ class AcceptRejectAllFloatingWidget extends Widget implements IOverlayWidget {
|
|||
}
|
||||
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: VOID_ACCEPT_DIFF_ACTION_ID,
|
||||
f1: true,
|
||||
title: localize2('voidAcceptDiffAction', 'Void: Accept Diff'),
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.Enter,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter },
|
||||
weight: KeybindingWeight.VoidExtension,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
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.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.Backspace,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace },
|
||||
weight: KeybindingWeight.VoidExtension,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
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) })
|
||||
}
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, 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 } 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
|
||||
|
|
@ -62,6 +63,9 @@ export interface IVoidSettingsService {
|
|||
setOptionsOfModelSelection: SetOptionsOfModelSelection;
|
||||
setGlobalSetting: SetGlobalSettingFn;
|
||||
|
||||
// setting to undefined CLEARS it, unlike others:
|
||||
setOverridesOfModel(providerName: ProviderName, modelName: string, overrides: Partial<ModelOverrides> | undefined): Promise<void>;
|
||||
|
||||
dangerousSetState(newState: VoidSettingsState): Promise<void>;
|
||||
resetState(): Promise<void>;
|
||||
|
||||
|
|
@ -94,8 +98,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, },
|
||||
|
|
@ -163,7 +174,7 @@ const _validatedModelState = (state: Omit<VoidSettingsState, '_modelOptions'>):
|
|||
for (const featureName of featureNames) {
|
||||
|
||||
const { filter } = modelFilterOfFeatureName[featureName]
|
||||
const filterOpts = { chatMode: state.globalSettings.chatMode }
|
||||
const filterOpts = { chatMode: state.globalSettings.chatMode, overridesOfModel: state.overridesOfModel }
|
||||
const modelOptionsForThisFeature = newModelOptions.filter((o) => filter(o.selection, filterOpts))
|
||||
|
||||
const modelSelectionAtFeature = newModelSelectionOfFeature[featureName]
|
||||
|
|
@ -182,6 +193,7 @@ const _validatedModelState = (state: Omit<VoidSettingsState, '_modelOptions'>):
|
|||
...state,
|
||||
settingsOfProvider: newSettingsOfProvider,
|
||||
modelSelectionOfFeature: newModelSelectionOfFeature,
|
||||
overridesOfModel: state.overridesOfModel,
|
||||
_modelOptions: newModelOptions,
|
||||
} satisfies VoidSettingsState
|
||||
|
||||
|
|
@ -198,6 +210,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 +280,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 +329,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 +355,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 +440,28 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
|||
this._onDidChangeState.fire()
|
||||
}
|
||||
|
||||
setOverridesOfModel = async (providerName: ProviderName, modelName: string, overrides: Partial<ModelOverrides> | undefined) => {
|
||||
const newState: VoidSettingsState = {
|
||||
...this.state,
|
||||
overridesOfModel: {
|
||||
...this.state.overridesOfModel,
|
||||
[providerName]: {
|
||||
...this.state.overridesOfModel[providerName],
|
||||
[modelName]: overrides === undefined ? undefined : {
|
||||
...this.state.overridesOfModel[providerName][modelName],
|
||||
...overrides
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.state = _validatedModelState(newState);
|
||||
await this._storeState();
|
||||
this._onDidChangeState.fire();
|
||||
|
||||
this._metricsService.capture('Update Model Overrides', { providerName, modelName, overrides });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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, ModelOverrides } from './modelCapabilities.js';
|
||||
import { ToolApprovalType } from './toolsServiceTypes.js';
|
||||
import { VoidSettingsState } from './voidSettingsService.js'
|
||||
|
||||
|
|
@ -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', }
|
||||
|
|
@ -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', }
|
||||
}
|
||||
|
|
@ -117,8 +117,8 @@ 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 === '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 === '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).'
|
||||
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<CustomSettingName, undefined> = {
|
||||
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,
|
||||
|
|
@ -462,13 +466,28 @@ export const globalSettingNames = Object.keys(defaultGlobalSettings) as GlobalSe
|
|||
export type ModelSelectionOptions = {
|
||||
reasoningEnabled?: boolean;
|
||||
reasoningBudget?: number;
|
||||
reasoningEffort?: string;
|
||||
}
|
||||
|
||||
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]: Partial<ModelOverrides> | undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const overridesOfModel = {} as OverridesOfModel
|
||||
for (const providerName of providerNames) { overridesOfModel[providerName] = {} }
|
||||
export const defaultOverridesOfModel = overridesOfModel
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -11,16 +11,24 @@ 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';
|
||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js';
|
||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.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';
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -31,6 +39,7 @@ type InternalCommonMessageParams = {
|
|||
providerName: ProviderName;
|
||||
settingsOfProvider: SettingsOfProvider;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
overridesOfModel: OverridesOfModel | undefined;
|
||||
modelName: string;
|
||||
_setAborter: (aborter: () => void) => void;
|
||||
}
|
||||
|
|
@ -50,6 +59,15 @@ const invalidApiKeyMessage = (providerName: ProviderName) => `Invalid ${displayI
|
|||
|
||||
|
||||
|
||||
const parseHeadersJSON = (s: string | undefined): Record<string, string | null | undefined> | 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 +105,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 +125,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]
|
||||
|
|
@ -125,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 })
|
||||
|
|
@ -211,20 +231,22 @@ 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 { canIOReasoning, openSourceThinkTags } = reasoningCapabilities || {}
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here
|
||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||
|
||||
console.log('include', includeInPayload)
|
||||
console.log('reasoningInfo', reasoningInfo)
|
||||
// tools
|
||||
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
|
||||
const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ?
|
||||
|
|
@ -396,21 +418,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 = getReservedOutputTokenSpace(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled, overridesOfModel })
|
||||
|
||||
// tools
|
||||
const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null
|
||||
|
|
@ -520,8 +542,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 })
|
||||
|
|
@ -660,6 +682,7 @@ const sendGeminiChat = async ({
|
|||
onFinalMessage,
|
||||
onError,
|
||||
settingsOfProvider,
|
||||
overridesOfModel,
|
||||
modelName: modelName_,
|
||||
_setAborter,
|
||||
providerName,
|
||||
|
|
@ -675,13 +698,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
|
||||
|
|
@ -843,20 +866,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,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const sendLLMMessage = async ({
|
|||
settingsOfProvider,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
overridesOfModel,
|
||||
chatMode,
|
||||
separateSystemMessage,
|
||||
}: SendLLMMessageParams,
|
||||
|
|
@ -106,15 +107,15 @@ 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: 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 })
|
||||
|
|
|
|||
Loading…
Reference in a new issue