diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts
index f9c50efa..faadd1cd 100644
--- a/src/vs/code/electron-main/app.ts
+++ b/src/vs/code/electron-main/app.ts
@@ -121,11 +121,11 @@ import { normalizeNFC } from '../../base/common/normalization.js';
import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js';
import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js';
-import { LLMMessageChannel } from '../../platform/void/electron-main/llmMessageChannel.js';
-import { IMetricsService } from '../../platform/void/common/metricsService.js';
-import { MetricsMainService } from '../../platform/void/electron-main/metricsMainService.js';
-import { VoidMainUpdateService } from '../../platform/void/electron-main/voidUpdateMainService.js';
-import { IVoidUpdateService } from '../../platform/void/common/voidUpdateService.js';
+import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/llmMessageChannel.js';
+import { IMetricsService } from '../../workbench/contrib/void/common/metricsService.js';
+import { MetricsMainService } from '../../workbench/contrib/void/electron-main/metricsMainService.js';
+import { VoidMainUpdateService } from '../../workbench/contrib/void/electron-main/voidUpdateMainService.js';
+import { IVoidUpdateService } from '../../workbench/contrib/void/common/voidUpdateService.js';
/**
* The main VS Code application. There will only ever be one instance,
* even if the user starts many instances (e.g. from the command line).
diff --git a/src/vs/platform/void/browser/void.contribution.ts b/src/vs/platform/void/browser/void.contribution.ts
deleted file mode 100644
index 276d6e72..00000000
--- a/src/vs/platform/void/browser/void.contribution.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*--------------------------------------------------------------------------------------
- * Copyright 2025 Glass Devtools, Inc. All rights reserved.
- * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
- *--------------------------------------------------------------------------------------*/
-
-// ---------- common ----------
-
-// llmMessage
-import '../common/llmMessageService.js'
-
-// voidSettings
-import '../common/voidSettingsService.js'
-
-// refreshModel
-import '../common/refreshModelService.js'
-
-// metrics
-import '../common/metricsService.js'
-
-// updates
-import '../common/voidUpdateService.js'
diff --git a/src/vs/platform/void/electron-main/llmMessage/greptile.ts b/src/vs/platform/void/electron-main/llmMessage/greptile.ts
deleted file mode 100644
index f61f87af..00000000
--- a/src/vs/platform/void/electron-main/llmMessage/greptile.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*--------------------------------------------------------------------------------------
- * Copyright 2025 Glass Devtools, Inc. All rights reserved.
- * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
- *--------------------------------------------------------------------------------------*/
-
-// // Greptile
-// // https://docs.greptile.com/api-reference/query
-// // https://docs.greptile.com/quickstart#sample-response-streamed
-
-// import { SendLLMMessageFnTypeInternal } from '../../common/llmMessageTypes.js';
-
-// export const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, _setAborter }) => {
-
-// let fullText = ''
-
-// const thisConfig = settingsOfProvider.greptile
-
-// fetch('https://api.greptile.com/v2/query', {
-// method: 'POST',
-// headers: {
-// 'Authorization': `Bearer ${thisConfig.apikey}`,
-// 'X-Github-Token': `${thisConfig.githubPAT}`,
-// 'Content-Type': `application/json`,
-// },
-// body: JSON.stringify({
-// messages,
-// stream: true,
-// repositories: [thisConfig.repoinfo],
-// }),
-// })
-// // this is {message}\n{message}\n{message}...\n
-// .then(async response => {
-// const text = await response.text()
-// console.log('got greptile', text)
-// return JSON.parse(`[${text.trim().split('\n').join(',')}]`)
-// })
-// // TODO make this actually stream, right now it just sends one message at the end
-// // TODO add _setAborter() when add streaming
-// .then(async responseArr => {
-
-// for (const response of responseArr) {
-// const type: string = response['type']
-// const message = response['message']
-
-// // when receive text
-// if (type === 'message') {
-// fullText += message
-// onText({ newText: message, fullText })
-// }
-// else if (type === 'sources') {
-// const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null }
-// fullText += filepath
-// onText({ newText: filepath, fullText })
-// }
-// // type: 'status' with an empty 'message' means last message
-// else if (type === 'status') {
-// if (!message) {
-// onFinalMessage({ fullText })
-// }
-// }
-// }
-
-// })
-// .catch(error => {
-// onError({ error })
-// });
-
-// }
diff --git a/src/vs/workbench/api/browser/mainThreadInlineDiff.ts b/src/vs/workbench/api/browser/mainThreadInlineDiff.ts
index 344458f9..d43fc633 100644
--- a/src/vs/workbench/api/browser/mainThreadInlineDiff.ts
+++ b/src/vs/workbench/api/browser/mainThreadInlineDiff.ts
@@ -14,6 +14,8 @@ import { WorkspaceEdit } from '../../../editor/common/languages.js';
// import { IHistoryService } from '../../services/history/common/history.js';
+// VOID: THIS FILE IS OUTDATED!!!!!! No longer used anywhere.
+
@extHostNamedCustomer(MainContext.MainThreadInlineDiff)
export class MainThreadInlineDiff extends Disposable implements MainThreadInlineDiffShape {
diff --git a/src/vs/workbench/contrib/void/browser/autocompleteService.ts b/src/vs/workbench/contrib/void/browser/autocompleteService.ts
index 6b67c6be..aa8902f3 100644
--- a/src/vs/workbench/contrib/void/browser/autocompleteService.ts
+++ b/src/vs/workbench/contrib/void/browser/autocompleteService.ts
@@ -11,7 +11,6 @@ import { Position } from '../../../../editor/common/core/position.js';
import { InlineCompletion, InlineCompletionContext, } from '../../../../editor/common/languages.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Range } from '../../../../editor/common/core/range.js';
-import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { EditorResourceAccessor } from '../../../common/editor.js';
@@ -19,6 +18,7 @@ import { IModelService } from '../../../../editor/common/services/model.js';
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
import { isWindows } from '../../../../base/common/platform.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
+import { ILLMMessageService } from '../common/llmMessageService.js';
// import { IContextGatheringService } from './contextGatheringService.js';
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
@@ -784,8 +784,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
_newlineCount: 0,
}
- console.log('BB')
- console.log('type', predictionType)
+ console.log('starting autocomplete...', predictionType)
// set parameters of `newAutocompletion` appropriately
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts
index d0ca28b2..a3452eb2 100644
--- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts
+++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts
@@ -11,7 +11,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
import { URI } from '../../../../base/common/uri.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IRange } from '../../../../editor/common/core/range.js';
-import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
+import { ILLMMessageService } from '../common/llmMessageService.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { chat_userMessage, chat_systemMessage } from './prompt/prompts.js';
@@ -33,6 +33,14 @@ export type FileSelection = {
export type StagingSelectionItem = CodeSelection | FileSelection
+export type StagingInfo = {
+ isBeingEdited: boolean;
+ selections: StagingSelectionItem[] | null; // staging selections in edit mode
+}
+
+const defaultStaging: StagingInfo = { isBeingEdited: false, selections: [] }
+
+
// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
export type ChatMessage =
| {
@@ -40,6 +48,7 @@ export type ChatMessage =
content: string | null; // content sent to the llm - allowed to be '', will be replaced with (empty)
displayContent: string | null; // content displayed to user - allowed to be '', will be ignored
selections: StagingSelectionItem[] | null; // the user's selection
+ staging: StagingInfo | null
}
| {
role: 'assistant';
@@ -59,13 +68,14 @@ export type ChatThreads = {
createdAt: string; // ISO string
lastModified: string; // ISO string
messages: ChatMessage[];
+ staging: StagingInfo | null;
+ focusedMessageIdx?: number | undefined; // index of the message that is being edited (undefined if none)
};
}
export type ThreadsState = {
allThreads: ChatThreads;
currentThreadId: string; // intended for internal use only
- currentStagingSelections: StagingSelectionItem[] | null;
}
export type ThreadStreamState = {
@@ -84,11 +94,16 @@ const newThreadObject = () => {
createdAt: now,
lastModified: now,
messages: [],
+ focusedMessageIdx: undefined,
+ staging: {
+ isBeingEdited: true,
+ selections: [],
+ }
} satisfies ChatThreads[string]
}
const THREAD_VERSION_KEY = 'void.chatThreadVersion'
-const THREAD_VERSION = 'v1'
+const THREAD_VERSION = 'v2'
const THREAD_STORAGE_KEY = 'void.chatThreadStorage'
@@ -105,8 +120,13 @@ export interface IChatThreadService {
openNewThread(): void;
switchToThread(threadId: string): void;
- setStaging(stagingSelection: StagingSelectionItem[] | null): void;
+ getFocusedMessageIdx(): number | undefined;
+ isFocusingMessage(): boolean;
+ setFocusedMessageIdx(messageIdx: number | undefined): void;
+ _useFocusedStagingState(messageIdx?: number | undefined): readonly [StagingInfo, (stagingInfo: StagingInfo) => void];
+
+ editUserMessageAndStreamResponse(userMessage: string, messageIdx: number): Promise;
addUserMessageAndStreamResponse(userMessage: string): Promise;
cancelStreaming(threadId: string): void;
dismissStreamError(threadId: string): void;
@@ -137,7 +157,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this.state = {
allThreads: this._readAllThreads(),
currentThreadId: null as unknown as string, // gets set in startNewThread()
- currentStagingSelections: null,
}
// always be in a thread
@@ -145,14 +164,71 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// for now just write the version, anticipating bigger changes in the future where we'll want to access this
this._storageService.store(THREAD_VERSION_KEY, THREAD_VERSION, StorageScope.APPLICATION, StorageTarget.USER)
+
}
private _readAllThreads(): ChatThreads {
// PUT ANY VERSION CHANGE FORMAT CONVERSION CODE HERE
// CAN ADD "v0" TAG IN STORAGE AND CONVERT
- const threads = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION)
- return threads ? JSON.parse(threads) : {}
+
+
+ const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION)
+
+ const threads: ChatThreads = threadsStr ? JSON.parse(threadsStr) : {}
+
+ this._updateThreadsToVersion(threads, THREAD_VERSION)
+
+ return threads
+ }
+
+
+ private _updateThreadsToVersion(oldThreadsObject: any, toVersion: string) {
+
+ if (toVersion === 'v2') {
+
+ const threads: ChatThreads = oldThreadsObject
+
+ /** v1 -> v2
+ - threadsState.currentStagingSelections: CodeStagingSelection[] | null;
+ + thread.staging: StagingInfo
+ + thread.focusedMessageIdx?: number | undefined;
+
+ + chatMessage.staging: StagingInfo | null
+ */
+
+ // check if we need to update
+ let shouldUpdate = false
+ for (const thread of Object.values(threads)) {
+ if (!thread.staging) {
+ shouldUpdate = true
+ }
+ for (const chatMessage of Object.values(thread.messages)) {
+ if (chatMessage.role === 'user' && !chatMessage.staging) {
+ shouldUpdate = true
+ }
+ }
+ }
+
+ if (!shouldUpdate) return;
+
+ // update the threads
+ for (const thread of Object.values(threads)) {
+ if (!thread.staging) {
+ thread.staging = defaultStaging
+ thread.focusedMessageIdx = undefined
+ }
+ for (const chatMessage of Object.values(thread.messages)) {
+ if (chatMessage.role === 'user' && !chatMessage.staging) {
+ chatMessage.staging = defaultStaging
+ }
+ }
+ }
+
+ // push the update
+ this._storeAllThreads(threads)
+ }
+
}
private _storeAllThreads(threads: ChatThreads) {
@@ -187,18 +263,51 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this._setStreamState(threadId, { messageSoFar: undefined, streamingToken: undefined, error })
}
- async addUserMessageAndStreamResponse(userMessage: string) {
- const threadId = this.getCurrentThread().id
- const currSelns = this.state.currentStagingSelections ?? []
+ async editUserMessageAndStreamResponse(userMessage: string, messageIdx: number) {
+
+ const thread = this.getCurrentThread()
+
+ const messageToReplace = thread.messages[messageIdx]
+ if (messageToReplace?.role !== 'user') {
+ console.log(`Error: tried to edit non-user message. messageIdx=${messageIdx}, numMessages=${thread.messages.length}`)
+ return
+ }
+
+ // clear messages up to the index
+ const slicedMessages = thread.messages.slice(0, messageIdx)
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [thread.id]: {
+ ...thread,
+ messages: slicedMessages
+ }
+ }
+ }, true)
+
+ // stream the edit
+ this.addUserMessageAndStreamResponse(userMessage, messageToReplace.staging)
+
+ }
+
+ async addUserMessageAndStreamResponse(userMessage: string, stagingOverride?: StagingInfo | null) {
+
+
+ const thread = this.getCurrentThread()
+ const threadId = thread.id
+
+ let threadStaging = thread.staging
+
+ const currStaging = stagingOverride ?? threadStaging ?? defaultStaging // don't use _useFocusedStagingState to avoid race conditions with focusing
+ const { selections: currSelns, } = currStaging
// add user's message to chat history
const instructions = userMessage
const content = await chat_userMessage(instructions, currSelns, this._modelService)
- const userHistoryElt: ChatMessage = { role: 'user', content: content, displayContent: instructions, selections: currSelns }
+ const userHistoryElt: ChatMessage = { role: 'user', content: content, displayContent: instructions, selections: currSelns, staging: null, }
this._addMessageToThread(threadId, userHistoryElt)
-
this._setStreamState(threadId, { error: undefined })
const llmCancelToken = this._llmMessageService.sendLLMMessage({
@@ -241,12 +350,29 @@ class ChatThreadService extends Disposable implements IChatThreadService {
getCurrentThread(): ChatThreads[string] {
const state = this.state
- return state.allThreads[state.currentThreadId];
+ return state.allThreads[state.currentThreadId]
+ }
+
+ getFocusedMessageIdx() {
+ const thread = this.getCurrentThread()
+
+ // get the focusedMessageIdx
+ const focusedMessageIdx = thread.focusedMessageIdx
+ if (focusedMessageIdx === undefined) return;
+
+ // check that the message is actually being edited
+ const focusedMessage = thread.messages[focusedMessageIdx]
+ if (focusedMessage.role !== 'user') return;
+ if (!focusedMessage.staging?.isBeingEdited) return;
+
+ return focusedMessageIdx
+ }
+
+ isFocusingMessage() {
+ return this.getFocusedMessageIdx() !== undefined
}
switchToThread(threadId: string) {
- // console.log('threadId', threadId)
- // console.log('messages', this.state.allThreads[threadId].messages)
this._setState({ currentThreadId: threadId }, true)
}
@@ -291,11 +417,93 @@ class ChatThreadService extends Disposable implements IChatThreadService {
this._setState({ allThreads: newThreads }, true) // the current thread just changed (it had a message added to it)
}
+ // sets the currently selected message (must be undefined if no message is selected)
+ setFocusedMessageIdx(messageIdx: number | undefined) {
- setStaging(stagingSelection: StagingSelectionItem[] | null): void {
- this._setState({ currentStagingSelections: stagingSelection }, true) // this is a hack for now
+ const threadId = this.state.currentThreadId
+ const thread = this.state.allThreads[threadId]
+ if (!thread) return
+
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [threadId]: {
+ ...thread,
+ focusedMessageIdx: messageIdx
+ }
+ }
+ }, true)
}
+ // set thread.messages[messageIdx].stagingSelections
+ private setEditMessageStaging(staging: StagingInfo, messageIdx: number): void {
+
+ const thread = this.getCurrentThread()
+ const message = thread.messages[messageIdx]
+ if (message.role !== 'user') return;
+
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [thread.id]: {
+ ...thread,
+ messages: thread.messages.map((m, i) =>
+ i === messageIdx ? {
+ ...m,
+ staging,
+ } : m
+ )
+ }
+ }
+ }, true)
+
+ }
+
+ // set thread.stagingSelections
+ private setDefaultStaging(staging: StagingInfo): void {
+
+ const thread = this.getCurrentThread()
+
+ this._setState({
+ allThreads: {
+ ...this.state.allThreads,
+ [thread.id]: {
+ ...thread,
+ staging,
+ }
+ }
+ }, true)
+
+ }
+
+ // gets `staging` and `setStaging` of the currently focused element, given the index of the currently selected message (or undefined if no message is selected)
+ _useFocusedStagingState(messageIdx?: number | undefined) {
+
+ const defaultStaging = { isBeingEdited: false, selections: [], text: '' }
+
+ let staging: StagingInfo = defaultStaging
+ let setStaging: (selections: StagingInfo) => void = () => { }
+
+ const thread = this.getCurrentThread()
+ const isFocusingMessage = messageIdx !== undefined
+ if (isFocusingMessage) { // is editing message
+
+ const message = thread.messages[messageIdx!]
+ if (message.role === 'user') {
+ staging = message.staging || defaultStaging
+ setStaging = (s) => this.setEditMessageStaging(s, messageIdx)
+ }
+
+ }
+ else { // is editing the default input box
+ staging = thread.staging || defaultStaging
+ setStaging = this.setDefaultStaging.bind(this)
+ }
+
+ return [staging, setStaging] as const
+ }
+
+
}
registerSingleton(IChatThreadService, ChatThreadService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts
index 768f4cfc..1cb53e5c 100644
--- a/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts
+++ b/src/vs/workbench/contrib/void/browser/helpers/extractCodeFromResult.ts
@@ -62,7 +62,7 @@ class SurroundingsRemover {
if (index === -1) {
this.i = this.j + 1
- return false
+ return null
}
// console.log('index', index, until.length)
diff --git a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts
index 60e5dc5c..b0f154d1 100644
--- a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts
+++ b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts
@@ -1,10 +1,17 @@
import { URI } from '../../../../../base/common/uri'
import { EndOfLinePreference } from '../../../../../editor/common/model'
import { IModelService } from '../../../../../editor/common/services/model.js'
+import { IFileService } from '../../../../../platform/files/common/files'
-// read files from VSCode
+// read files from VSCode. preferred (but appears to only work if the model of this URI already exists. If it doesn't use the other function.)
export const VSReadFile = async (modelService: IModelService, uri: URI): Promise => {
const model = modelService.getModel(uri)
if (!model) return null
return model.getValue(EndOfLinePreference.LF)
}
+
+export const VSReadFileRaw = async (fileService: IFileService, uri: URI) => {
+ const res = await fileService.readFile(uri)
+ const str = res.value.toString()
+ return str
+}
diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts
index d4d7d065..ed435a26 100644
--- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts
+++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts
@@ -25,15 +25,12 @@ import * as dom from '../../../../base/browser/dom.js';
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, fastApply_userMessage, fastApply_systemMessage, defaultFimTags } from './prompt/prompts.js';
-import { ILLMMessageService } from '../../../../platform/void/common/llmMessageService.js';
+import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, fastApply_rewritewholething_userMessage, fastApply_rewritewholething_systemMessage, defaultQuickEditFimTags, tripleTick } from './prompt/prompts.js';
import { mountCtrlK } from '../browser/react/out/quick-edit-tsx/index.js'
import { QuickEditPropsType } from './quickEditActions.js';
-import { errorDetails, LLMChatMessage } from '../../../../platform/void/common/llmMessageTypes.js';
import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js';
import { extractCodeFromFIM, extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
-import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { filenameToVscodeLanguage } from './helpers/detectLanguage.js';
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
import { isMacintosh } from '../../../../base/common/platform.js';
@@ -41,6 +38,10 @@ import { EditorOption } from '../../../../editor/common/config/editorOptions.js'
import { Emitter } from '../../../../base/common/event.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
+import { ILLMMessageService } from '../common/llmMessageService.js';
+import { LLMChatMessage, _InternalLLMChatMessage, errorDetails } from '../common/llmMessageTypes.js';
+import { IMetricsService } from '../common/metricsService.js';
+import { VSReadFile } from './helpers/readFile.js';
const configOfBG = (color: Color) => {
return { dark: color, light: color, hcDark: color, hcLight: color, }
@@ -100,19 +101,19 @@ const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number
return paddingLeft;
};
-// similar to ServiceLLM
+
+
export type StartApplyingOpts = {
from: 'QuickEdit';
+ type: 'rewrite';
diffareaid: number; // id of the CtrlK area (contains text selection)
} | {
- from: 'Chat';
+ from: 'ClickApply';
+ type: 'searchReplace' | 'rewrite';
applyStr: string;
-} | {
- from: 'Autocomplete';
- range: IRange;
- userMessage: string;
}
+
export type AddCtrlKOpts = {
startLine: number,
endLine: number,
@@ -137,6 +138,11 @@ export type Diff = {
+type ExtractedCodeBlock = {
+ state: 'writingOriginal' | 'writingFinal' | 'done',
+ orig: string,
+ final: string,
+}
// _ means anything we don't include if we clone it
// DiffArea.originalStartLine is the line in originalCode (not the file)
@@ -996,7 +1002,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// @throttle(100)
- private _writeStreamedDiffZoneLLMText(diffZone: DiffZone, llmText: string, deltaText: string, latest: { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number }) {
+ private _writeStreamedDiffZoneLLMText(diffZone: DiffZone, llmText: string, deltaText: string, latestMutable: { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number }) {
// ----------- 1. Write the new code to the document -----------
// figure out where to highlight based on where the AI is in the stream right now, use the last diff to figure that out
@@ -1035,39 +1041,39 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// at the start, add a newline between the stream and originalCode to make reasoning easier
- if (!latest.addedSplitYet) {
+ if (!latestMutable.addedSplitYet) {
this._writeText(uri, '\n',
- { startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line, endColumn: latest.col, },
+ { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line, endColumn: latestMutable.col, },
{ shouldRealignDiffAreas: true }
)
- latest.addedSplitYet = true
+ latestMutable.addedSplitYet = true
}
// insert deltaText at latest line and col
this._writeText(uri, deltaText,
- { startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line, endColumn: latest.col },
+ { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line, endColumn: latestMutable.col },
{ shouldRealignDiffAreas: true }
)
- latest.line += deltaText.split('\n').length - 1
+ latestMutable.line += deltaText.split('\n').length - 1
const lastNewlineIdx = deltaText.lastIndexOf('\n')
- latest.col = lastNewlineIdx === -1 ? latest.col + deltaText.length : deltaText.length - lastNewlineIdx
+ latestMutable.col = lastNewlineIdx === -1 ? latestMutable.col + deltaText.length : deltaText.length - lastNewlineIdx
// delete or insert to get original up to speed
- if (latest.originalCodeStartLine < originalCodeStartLine) {
+ if (latestMutable.originalCodeStartLine < originalCodeStartLine) {
// moved up, delete
- const numLinesDeleted = originalCodeStartLine - latest.originalCodeStartLine
+ const numLinesDeleted = originalCodeStartLine - latestMutable.originalCodeStartLine
this._writeText(uri, '',
- { startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line + numLinesDeleted, endColumn: Number.MAX_SAFE_INTEGER, },
+ { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line + numLinesDeleted, endColumn: Number.MAX_SAFE_INTEGER, },
{ shouldRealignDiffAreas: true }
)
}
- else if (latest.originalCodeStartLine > originalCodeStartLine) {
- this._writeText(uri, '\n' + diffZone.originalCode.split('\n').slice((originalCodeStartLine - 1), (latest.originalCodeStartLine - 1) - 1 + 1).join('\n'),
- { startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line, endColumn: latest.col },
+ else if (latestMutable.originalCodeStartLine > originalCodeStartLine) {
+ this._writeText(uri, '\n' + diffZone.originalCode.split('\n').slice((originalCodeStartLine - 1), (latestMutable.originalCodeStartLine - 1) - 1 + 1).join('\n'),
+ { startLineNumber: latestMutable.line, startColumn: latestMutable.col, endLineNumber: latestMutable.line, endColumn: latestMutable.col },
{ shouldRealignDiffAreas: true }
)
}
- latest.originalCodeStartLine = originalCodeStartLine
+ latestMutable.originalCodeStartLine = originalCodeStartLine
// add diffZone.startLine to convert to right coordinate system (line in file, not in diffarea)
diffZone._streamState.line = (diffZone.startLine - 1) + newCodeEndLine
@@ -1078,56 +1084,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
- // // if streaming, use diffs to figure out where to write new code
- // // these are two different coordinate systems - new and old line number
- // let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted
- // let originalCodeStartLine: number // get original[oldStartingPoint...]
-
- // const lastDiff = computedDiffs.pop()
-
- // if (!lastDiff) {
- // // if the writing is identical so far, display no changes
- // newFileEndLine = diffZone.startLine
- // originalCodeStartLine = 1
- // }
- // else {
- // if (lastDiff.type === 'insertion') {
- // newFileEndLine = lastDiff.endLine
- // originalCodeStartLine = lastDiff.originalStartLine
- // }
- // else if (lastDiff.type === 'deletion') {
- // newFileEndLine = lastDiff.startLine
- // originalCodeStartLine = lastDiff.originalStartLine
- // }
- // else if (lastDiff.type === 'edit') {
- // newFileEndLine = lastDiff.endLine
- // originalCodeStartLine = lastDiff.originalStartLine
- // }
- // else {
- // throw new Error(`Void: diff.type not recognized on: ${lastDiff}`)
- // }
- // }
-
- // diffZone._streamState.line = newFileEndLine
-
- // // lines are 1-indexed
- // const newFileTop = llmText.split('\n').slice(diffZone.startLine, (newFileEndLine - 1)).join('\n')
- // const oldFileBottom = diffZone.originalCode.split('\n').slice((originalCodeStartLine - 1), Infinity).join('\n')
-
- // const newCode = `${newFileTop}\n${oldFileBottom}`
-
- // this._writeText(uri, newCode,
- // { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER, }, // 1-indexed
- // { shouldRealignDiffAreas: true }
- // )
-
-
- // return computedDiffs
-
-
-
-
-
// called first, then call startApplying
public addCtrlKZone({ startLine, endLine, editor }: AddCtrlKOpts) {
@@ -1185,8 +1141,19 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
public startApplying(opts: StartApplyingOpts) {
- const addedDiffZone = this._initializeStartApplying(opts)
- return addedDiffZone?.diffareaid
+
+ if (opts.type === 'rewrite') {
+ const addedDiffZone = this._initializeRewriteStream(opts)
+ return addedDiffZone?.diffareaid
+ }
+
+ else if (opts.type === 'searchReplace') {
+ this._initializeSearchAndReplaceStream(opts)
+ return undefined
+ }
+
+ else return undefined
+
}
@@ -1207,7 +1174,243 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
}
- private _initializeStartApplying(opts: StartApplyingOpts): DiffZone | undefined {
+ private async _initializeSearchAndReplaceStream({ applyStr }: { applyStr: string }) {
+ const ORIGINAL = `<<<<<<< ORIGINAL`
+ const DIVIDER = `=======`
+ const FINAL = `>>>>>>> UPDATED`
+
+ const searchReplaceSysMessage = `\
+You are a coding assistant that generates SEARCH/REPLACE code blocks that will be used to edit a file.
+
+A SEARCH/REPLACE block describes the code before and after a change. Here is the format:
+${ORIGINAL}
+// ... original code goes here
+${DIVIDER}
+// ... final code goes here
+${FINAL}
+
+You will be given the original file \`ORIGINAL_FILE\` and a description of a change \`CHANGE\` to make.
+Output SEARCH/REPLACE blocks to edit the file according to the desired change. You may output multiple SEARCH/REPLACE blocks.
+
+Directions:
+1. Your OUTPUT should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this.
+2. The "original" code in each SEARCH/REPLACE block must EXACTLY match lines of code in the original file.
+3. The "original" code in each SEARCH/REPLACE block should include enough text to uniquely identify the change in the file.
+4. The SEARCH/REPLACE blocks you generate will be applied immediately, and so they **MUST** produce a file that the user can run IMMEDIATELY.
+ - Make sure you add all necessary imports.
+ - Make sure the "final" code is complete and will not result in syntax/lint errors.
+5. Follow coding convention (spaces, semilcolons, comments, etc).
+
+## EXAMPLE 1
+ORIGINAL_FILE
+${tripleTick[0]}
+let w = 5
+let x = 6
+let y = 7
+let z = 8
+${tripleTick[1]}
+
+CHANGE
+Make x equal to 6.5, not 6.
+${tripleTick[0]}
+// ... existing code
+let x = 6.5
+// ... existing code
+${tripleTick[1]}
+
+
+## ACCEPTED OUTPUT
+${tripleTick[0]}
+${ORIGINAL}
+let x = 6
+${DIVIDER}
+let x = 6.5
+${FINAL}
+${tripleTick[1]}
+`
+
+ const uri_ = this._getActiveEditorURI()
+ if (!uri_) return
+ const uri = uri_
+
+ // generate search/replace block text
+ const fileContents = await VSReadFile(this._modelService, uri)
+ if (fileContents === null) return
+
+
+ const searchReplaceUserMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\
+ORIGINAL_FILE
+${originalCode}
+
+CHANGE
+${applyStr}
+
+INSTRUCTIONS
+Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggested SEARCH/REPLACE blocks, without any explanation.
+`
+
+ const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => {
+ // for each prefix
+ for (let i = anyPrefix.length; i >= 0; i--) {
+ const prefix = anyPrefix.slice(0, i)
+ if (str.endsWith(prefix)) return prefix
+ }
+ return null
+ }
+
+ const extractBlocks = (str: string) => {
+
+ const ORIGINAL_ = ORIGINAL + `\n`
+ const DIVIDER_ = '\n' + DIVIDER + `\n`
+ const FINAL_ = '\n' + FINAL
+
+
+ const blocks: ExtractedCodeBlock[] = []
+
+ let i = 0 // search i and beyond (this is done by plain index, not by line number. much simpler this way)
+ while (true) {
+ let origStart = str.indexOf(ORIGINAL_, i)
+ if (origStart === -1) { return blocks }
+ origStart += ORIGINAL_.length
+ i = origStart
+ // wrote <<<< ORIGINAL
+
+ let dividerStart = str.indexOf(DIVIDER_, i)
+ if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now
+ const isWritingDIVIDER = endsWithAnyPrefixOf(str, DIVIDER_)
+ blocks.push({
+ orig: str.substring(origStart, str.length - (isWritingDIVIDER?.length ?? 0)),
+ final: '',
+ state: 'writingOriginal'
+ })
+ return blocks
+ }
+ const origStrDone = str.substring(origStart, dividerStart)
+ dividerStart += DIVIDER_.length
+ i = dividerStart
+ // wrote =====
+
+ let finalStart = str.indexOf(FINAL_, i)
+ if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL_ right now
+ const isWritingFINAL = endsWithAnyPrefixOf(str, FINAL_)
+ blocks.push({
+ orig: origStrDone,
+ final: str.substring(origStart, str.length - (isWritingFINAL?.length ?? 0)),
+ state: 'writingFinal'
+ })
+ return blocks
+ }
+ const finalStrDone = str.substring(dividerStart, finalStart)
+ finalStart += FINAL_.length
+ i = finalStart
+ // wrote >>>>> FINAL
+
+ blocks.push({
+ orig: origStrDone,
+ final: finalStrDone,
+ state: 'done'
+ })
+ }
+ }
+
+
+ // reject all diffZones on this URI, adding to history (there can't possibly be overlap after this)
+ this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: true })
+
+ const userMessageContent = searchReplaceUserMessage({ originalCode: fileContents, applyStr: applyStr })
+ const messages: LLMChatMessage[] = [
+ { role: 'system', content: searchReplaceSysMessage },
+ { role: 'user', content: userMessageContent }
+ ]
+ let streamRequestIdRef: { current: string | null } = { current: null }
+
+ const diffareaidOfBlockNum: number[] = []
+
+ const onText = ({ newText, fullText }: { newText: string, fullText: string }) => {
+ const blocks = extractBlocks(fullText)
+
+ // find block.orig in fileContents and return its range in file
+ const findTextInCode = (text: string, fileContents: string) => {
+ const idx = fileContents.indexOf(text)
+ if (idx === -1) return 'Not found' as const
+ const lastIdx = fileContents.lastIndexOf(text)
+ if (lastIdx !== idx) return 'Not unique' as const
+ const startLine = fileContents.substring(0, idx).split('\n').length
+ const numLines = text.split('\n').length
+ const endLine = startLine + numLines - 1
+ return [startLine, endLine]
+ }
+
+ let latestStreamInfoMutable: any = {}
+
+ for (let blockNum = 0; blockNum < blocks.length; blockNum += 1) {
+ const block = blocks[blockNum]
+ if (block.state === 'writingOriginal') continue
+
+ const foundInCode = findTextInCode(block.orig, fileContents)
+ if (typeof foundInCode === 'string') {
+ console.log('ERROR!!!!', foundInCode)
+ continue
+ }
+
+ const [startLine, endLine] = foundInCode
+
+ // if should add new diffarea
+ if (blockNum > diffareaidOfBlockNum.length) {
+ const adding: Omit = {
+ type: 'DiffZone',
+ originalCode: block.orig,
+ startLine,
+ endLine,
+ _URI: uri,
+ _streamState: {
+ isStreaming: true,
+ streamRequestIdRef,
+ line: startLine,
+ },
+ _diffOfId: {}, // added later
+ _removeStylesFns: new Set(),
+ }
+ const diffZone = this._addDiffArea(adding)
+ this._onDidChangeStreaming.fire({ uri, diffareaid: diffZone.diffareaid })
+ this._onDidAddOrDeleteDiffZones.fire({ uri })
+
+ diffareaidOfBlockNum.push(diffZone.diffareaid)
+
+ latestStreamInfoMutable = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
+ }
+
+ const diffareaid = diffareaidOfBlockNum[blockNum]
+ const diffZone = this.diffAreaOfId[diffareaid]
+ if (diffZone.type !== 'DiffZone') continue
+
+ this._writeStreamedDiffZoneLLMText(diffZone, fullText, newText, latestStreamInfoMutable)
+ this._refreshStylesAndDiffsInURI(uri)
+ }
+
+ }
+
+
+
+
+ // TODO turn this into a service and provide it
+ streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
+ messagesType: 'chatMessages',
+ useProviderFor: 'FastApply',
+ logging: { loggingName: `generateSearchAndReplace` },
+ messages,
+ onText: ({ newText, fullText }) => { onText({ newText, fullText }) },
+ onFinalMessage: ({ fullText }) => { },
+ onError: (e) => { console.log('ERROR', e) },
+
+ })
+
+ }
+
+
+
+
+ private _initializeRewriteStream(opts: StartApplyingOpts): DiffZone | undefined {
const { from } = opts
@@ -1215,7 +1418,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
let endLine: number
let uri: URI
- if (from === 'Chat') {
+ if (from === 'ClickApply') {
const uri_ = this._getActiveEditorURI()
if (!uri_) return
@@ -1257,8 +1460,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const { onFinishEdit } = this._addToHistory(uri)
// __TODO__ let users customize modelFimTags
- const isOllamaFIM = false // this._voidSettingsService.state.modelSelectionOfFeature['Ctrl+K']?.providerName === 'ollama'
- const modelFimTags = defaultFimTags
+ const quickEditFIMTags = defaultQuickEditFimTags
const adding: Omit = {
type: 'DiffZone',
@@ -1289,10 +1491,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// now handle messages
let messages: LLMChatMessage[]
- if (from === 'Chat') {
- const userContent = fastApply_userMessage({ originalCode, applyStr: opts.applyStr, uri })
+ if (from === 'ClickApply') {
+ const userContent = fastApply_rewritewholething_userMessage({ originalCode, applyStr: opts.applyStr, uri })
messages = [
- { role: 'system', content: fastApply_systemMessage, },
+ { role: 'system', content: fastApply_rewritewholething_systemMessage, },
{ role: 'user', content: userContent, }
]
}
@@ -1303,25 +1505,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const { _mountInfo } = ctrlKZone
const instructions = _mountInfo?.textAreaRef.current?.value ?? ''
- // __TODO__ use Ollama's FIM api, if (isOllamaFIM) {...} else:
const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine })
- // if (isOllamaFIM) {
- // messages = {
- // type: 'ollamaFIM',
- // prefix,
- // suffix,
- // }
-
- // }
- // else {
const language = filenameToVscodeLanguage(uri.fsPath) ?? ''
- const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, isOllamaFIM: false, fimTags: modelFimTags, language })
+ const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, isOllamaFIM: false, fimTags: quickEditFIMTags, language })
// type: 'messages',
messages = [
- { role: 'system', content: ctrlKStream_systemMessage({ fimTags: modelFimTags }), },
+ { role: 'system', content: ctrlKStream_systemMessage({ quickEditFIMTags: quickEditFIMTags }), },
{ role: 'user', content: userContent, }
]
- // }
}
else { throw new Error(`featureName ${from} is invalid`) }
@@ -1351,16 +1542,15 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const extractText = (fullText: string, recentlyAddedTextLen: number) => {
if (from === 'QuickEdit') {
- if (isOllamaFIM) return fullText
- return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: modelFimTags.midTag })
+ return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: quickEditFIMTags.midTag })
}
- else if (from === 'Chat') {
+ else if (from === 'ClickApply') {
return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen })
}
throw 1
}
- const latestStreamInfo = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
+ const latestStreamInfoMutable = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
// state used in onText:
let fullText = ''
@@ -1368,7 +1558,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
messagesType: 'chatMessages',
- useProviderFor: opts.from === 'Chat' ? 'FastApply' : 'Ctrl+K',
+ useProviderFor: opts.from === 'ClickApply' ? 'FastApply' : 'Ctrl+K',
logging: { loggingName: `startApplying - ${from}` },
messages,
onText: ({ newText: newText_ }) => {
@@ -1377,7 +1567,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
fullText += prevIgnoredSuffix + newText
const [text, deltaText, ignoredSuffix] = extractText(fullText, newText.length)
- this._writeStreamedDiffZoneLLMText(diffZone, text, deltaText, latestStreamInfo)
+ this._writeStreamedDiffZoneLLMText(diffZone, text, deltaText, latestStreamInfoMutable)
this._refreshStylesAndDiffsInURI(uri)
prevIgnoredSuffix = ignoredSuffix
diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts
index 7dc23d53..428625fd 100644
--- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts
+++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts
@@ -10,6 +10,10 @@ import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThrea
import { VSReadFile } from '../helpers/readFile.js';
import { IModelService } from '../../../../../editor/common/services/model.js';
+
+// this is just for ease of readability
+export const tripleTick = ['```', '```']
+
export const chat_systemMessage = `\
You are a coding assistant. You are given a list of instructions to follow \`INSTRUCTIONS\`, and optionally a list of relevant files \`FILES\`, and selections inside of files \`SELECTIONS\`.
@@ -29,7 +33,7 @@ Do not tell the user anything about the examples below.
## EXAMPLE 1
FILES
math.ts
-\`\`\`typescript
+${tripleTick[0]}typescript
const addNumbers = (a, b) => a + b
const multiplyNumbers = (a, b) => a * b
const subtractNumbers = (a, b) => a - b
@@ -58,21 +62,21 @@ const normalized = (vector: number[]) => {
const v2 = [...vector] // clone vector
return normalize(v2)
}
-\`\`\`
+${tripleTick[1]}
SELECTIONS
math.ts (lines 3:3)
-\`\`\`typescript
+${tripleTick[0]}typescript
const subtractNumbers = (a, b) => a - b
-\`\`\`
+${tripleTick[1]}
INSTRUCTIONS
add a function that exponentiates a number below this, and use it to make a power function that raises all entries of a vector to a power
-ACCEPTED OUTPUT
+## ACCEPTED OUTPUT
We can add the following code to the file:
-\`\`\`typescript
+${tripleTick[0]}typescript
// existing code...
const subtractNumbers = (a, b) => a - b
const exponentiateNumbers = (a, b) => Math.pow(a, b)
@@ -84,13 +88,13 @@ const raiseAll = (vector: number[], power: number) => {
vector[i] = exponentiateNumbers(vector[i], power)
return vector
}
-\`\`\`
+${tripleTick[1]}
## EXAMPLE 2
FILES
fib.ts
-\`\`\`typescript
+${tripleTick[0]}typescript
const dfs = (root) => {
if (!root) return;
@@ -102,20 +106,20 @@ const fib = (n) => {
if (n < 1) return 1
return fib(n - 1) + fib(n - 2)
}
-\`\`\`
+${tripleTick[1]}
SELECTIONS
fib.ts (lines 10:10)
-\`\`\`typescript
+${tripleTick[0]}typescript
return fib(n - 1) + fib(n - 2)
-\`\`\`
+${tripleTick[1]}
INSTRUCTIONS
memoize results
-ACCEPTED OUTPUT
+## ACCEPTED OUTPUT
To implement memoization in your Fibonacci function, you can use a JavaScript object to store previously computed results. This will help avoid redundant calculations and improve performance. Here's how you can modify your function:
-\`\`\`typescript
+${tripleTick[0]}typescript
// existing code...
const fib = (n, memo = {}) => {
if (n < 1) return 1;
@@ -123,7 +127,7 @@ const fib = (n, memo = {}) => {
memo[n] = fib(n - 1, memo) + fib(n - 2, memo); // Store result in memo
return memo[n];
}
-\`\`\`
+${tripleTick[1]}
Explanation:
Memoization Object: A memo object is used to store the results of Fibonacci calculations for each n.
Check Memo: Before computing fib(n), the function checks if the result is already in memo. If it is, it returns the stored result.
@@ -133,21 +137,21 @@ Store Result: After computing fib(n), the result is stored in memo for future re
`
-type FileSelnLocal = FileSelection & { content: string }
-const stringifyFileSelection = ({ fileURI, selectionStr, range, content }: FileSelnLocal) => {
+type FileSelnLocal = { fileURI: URI, content: string }
+const stringifyFileSelection = ({ fileURI, content }: FileSelnLocal) => {
return `\
${fileURI.fsPath}
-\`\`\`${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
+${tripleTick[0]}${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
${content}
-\`\`\`
+${tripleTick[1]}
`
}
const stringifyCodeSelection = ({ fileURI, selectionStr, range }: CodeSelection) => {
return `\
${fileURI.fsPath} (lines ${range.startLineNumber}:${range.endLineNumber})
-\`\`\`${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
+${tripleTick[0]}${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}
${selectionStr}
-\`\`\`
+${tripleTick[1]}
`
}
@@ -183,7 +187,7 @@ export const chat_userMessage = async (instructions: string, selections: Staging
-export const fastApply_systemMessage = `\
+export const fastApply_rewritewholething_systemMessage = `\
You are a coding assistant that re-writes an entire file to make a change. You are given the original file \`ORIGINAL_FILE\` and a change \`CHANGE\`.
Directions:
@@ -195,20 +199,20 @@ Directions:
-export const fastApply_userMessage = ({ originalCode, applyStr, uri }: { originalCode: string, applyStr: string, uri: URI }) => {
+export const fastApply_rewritewholething_userMessage = ({ originalCode, applyStr, uri }: { originalCode: string, applyStr: string, uri: URI }) => {
const language = filenameToVscodeLanguage(uri.fsPath) ?? ''
return `\
ORIGINAL_FILE
-\`\`\`${language}
+${tripleTick[0]}${language}
${originalCode}
-\`\`\`
+${tripleTick[1]}
CHANGE
-\`\`\`
+${tripleTick[0]}
${applyStr}
-\`\`\`
+${tripleTick[1]}
INSTRUCTIONS
Please finish writing the new file by applying the change to the original file. Return ONLY the completion of the file, without any explanation.
@@ -220,6 +224,12 @@ Please finish writing the new file by applying the change to the original file.
+
+
+
+
+
+
export const voidPrefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullFileStr: string, startLine: number, endLine: number }) => {
const fullFileLines = fullFileStr.split('\n')
@@ -269,19 +279,19 @@ export const voidPrefixAndSuffix = ({ fullFileStr, startLine, endLine }: { fullF
}
-export type FimTagsType = {
+export type QuickEditFimTagsType = {
preTag: string,
sufTag: string,
midTag: string
}
-export const defaultFimTags: FimTagsType = {
+export const defaultQuickEditFimTags: QuickEditFimTagsType = {
preTag: 'ABOVE',
sufTag: 'BELOW',
midTag: 'SELECTION',
}
// this should probably be longer
-export const ctrlKStream_systemMessage = ({ fimTags: { preTag, midTag, sufTag } }: { fimTags: FimTagsType }) => {
+export const ctrlKStream_systemMessage = ({ quickEditFIMTags: { preTag, midTag, sufTag } }: { quickEditFIMTags: QuickEditFimTagsType }) => {
return `\
You are a FIM (fill-in-the-middle) coding assistant. Your task is to fill in the middle SELECTION marked by <${midTag}> tags.
@@ -297,7 +307,7 @@ Instructions:
}
export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instructions, fimTags, isOllamaFIM, language }: {
- selection: string, prefix: string, suffix: string, instructions: string, fimTags: FimTagsType, language: string,
+ selection: string, prefix: string, suffix: string, instructions: string, fimTags: QuickEditFimTagsType, language: string,
isOllamaFIM: false, // we require this be false for clarity
}) => {
const { preTag, sufTag, midTag } = fimTags
@@ -309,9 +319,9 @@ export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instruction
return `\
CURRENT SELECTION
-\`\`\`${language}
+${tripleTick[0]}${language}
<${midTag}>${selection}${midTag}>
-\`\`\`
+${tripleTick[1]}
INSTRUCTIONS
${instructions}
@@ -319,8 +329,8 @@ ${instructions}
<${preTag}>${prefix}${preTag}>
<${sufTag}>${suffix}${sufTag}>
-Return only the completion block of code (of the form \`\`\`${language}
+Return only the completion block of code (of the form ${tripleTick[0]}${language}
<${midTag}>...new code${midTag}>
-\`\`\`).`
+${tripleTick[1]}).`
};
diff --git a/src/vs/workbench/contrib/void/browser/quickEditActions.ts b/src/vs/workbench/contrib/void/browser/quickEditActions.ts
index 1498c8f6..1a6e0deb 100644
--- a/src/vs/workbench/contrib/void/browser/quickEditActions.ts
+++ b/src/vs/workbench/contrib/void/browser/quickEditActions.ts
@@ -7,12 +7,12 @@ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
-import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { IInlineDiffsService } from './inlineDiffsService.js';
import { roundRangeToLines } from './sidebarActions.js';
import { VOID_CTRL_K_ACTION_ID } from './actionIDs.js';
import { localize2 } from '../../../../nls.js';
+import { IMetricsService } from '../common/metricsService.js';
export type QuickEditPropsType = {
diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
index 28cd2539..7b03f068 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx
@@ -6,7 +6,8 @@
import React, { JSX, useCallback, useEffect, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
-import { useAccessor } from '../util/services.js'
+import { useAccessor, useChatThreadsState, useChatThreadsStreamState } from '../util/services.js'
+import { ChatMessageLocation, } from '../../../searchAndReplaceService.js'
import { nameToVscodeLanguage } from '../../../helpers/detectLanguage.js'
@@ -18,7 +19,17 @@ enum CopyButtonState {
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
-const CodeButtonsOnHover = ({ text }: { text: string }) => {
+
+
+type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: number }
+
+const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => {
+ return `${threadId}-${messageIdx}-${tokenIdx}`
+}
+
+
+
+const ApplyButtonsOnHover = ({ applyStr, applyBoxId }: { applyStr: string, applyBoxId: string }) => {
const accessor = useAccessor()
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
@@ -36,22 +47,24 @@ const CodeButtonsOnHover = ({ text }: { text: string }) => {
}, [copyButtonState])
const onCopy = useCallback(() => {
- clipboardService.writeText(text)
+ clipboardService.writeText(applyStr)
.then(() => { setCopyButtonState(CopyButtonState.Copied) })
.catch(() => { setCopyButtonState(CopyButtonState.Error) })
- metricsService.capture('Copy Code', { length: text.length }) // capture the length only
+ metricsService.capture('Copy Code', { length: applyStr.length }) // capture the length only
- }, [metricsService, clipboardService, text])
+ }, [metricsService, clipboardService, applyStr])
const onApply = useCallback(() => {
- inlineDiffService.startApplying({
- from: 'Chat',
- applyStr: text,
- })
- metricsService.capture('Apply Code', { length: text.length }) // capture the length only
- }, [metricsService, inlineDiffService, text])
- const isSingleLine = !text.includes('\n')
+ inlineDiffService.startApplying({
+ from: 'ClickApply',
+ type: 'searchReplace',
+ applyStr,
+ })
+ metricsService.capture('Apply Code', { length: applyStr.length }) // capture the length only
+ }, [metricsService, inlineDiffService, applyStr])
+
+ const isSingleLine = !applyStr.includes('\n')
return <>
)
}
@@ -266,12 +289,12 @@ const RenderToken = ({ token, nested = false, noSpace = false }: { token: Token
)
}
-export const ChatMarkdownRender = ({ string, nested = false, noSpace }: { string: string, nested?: boolean, noSpace?: boolean }) => {
+export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatMessageLocation }: { string: string, nested?: boolean, noSpace?: boolean, chatMessageLocation?: ChatMessageLocation }) => {
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
return (
<>
{tokens.map((token, index) => (
-
+
))}
>
)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx
index 7edf1e5b..57dbb472 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx
@@ -12,7 +12,7 @@ import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
import { useRefState } from '../util/helpers.js';
import { useScrollbarStyles } from '../util/useScrollbarStyles.js';
-import { isFeatureNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
+import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
export const QuickEditChat = ({
diffareaid,
@@ -59,6 +59,7 @@ export const QuickEditChat = ({
const id = inlineDiffsService.startApplying({
from: 'QuickEdit',
+ type:'rewrite',
diffareaid: diffareaid,
})
setCurrentlyStreamingDiffZone(id ?? null)
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx
index 425ce3c9..9aef4b72 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/ErrorDisplay.tsx
@@ -5,7 +5,7 @@
import React, { useEffect, useState } from 'react';
import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
-import { errorDetails } from '../../../../../../../platform/void/common/llmMessageTypes.js';
+import { errorDetails } from '../../../../../../../workbench/contrib/void/common/llmMessageTypes.js';
import { useSettingsState } from '../util/services.js';
diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
index 9874aa3c..d8b4ef93 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
@@ -7,7 +7,7 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K
import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js';
-import { ChatMessage, StagingSelectionItem } from '../../../chatThreadService.js';
+import { ChatMessage, StagingInfo, StagingSelectionItem } from '../../../chatThreadService.js';
import { BlockCode } from '../markdown/BlockCode.js';
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
@@ -21,9 +21,11 @@ import { useScrollbarStyles } from '../util/useScrollbarStyles.js';
import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
import { filenameToVscodeLanguage } from '../../../helpers/detectLanguage.js';
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
-import { Pencil } from 'lucide-react';
-import { FeatureName, isFeatureNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js';
+import { Pencil, X } from 'lucide-react';
+import { FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
+import { ChatMessageLocation } from '../../../searchAndReplaceService.js';
+
export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps) => {
@@ -145,15 +147,19 @@ interface VoidChatAreaProps {
onAbort: () => void;
isStreaming: boolean;
isDisabled?: boolean;
- divRef: React.RefObject;
+ divRef?: React.RefObject;
// UI customization
featureName: FeatureName;
className?: string;
showModelDropdown?: boolean;
showSelections?: boolean;
- selections?: any[];
- onSelectionsChange?: (selections: any[]) => void;
+ showProspectiveSelections?: boolean;
+
+ staging?: StagingInfo
+ setStaging?: (s: StagingInfo) => void
+ // selections?: any[];
+ // onSelectionsChange?: (selections: any[]) => void;
onClickAnywhere?: () => void;
// Optional close button
@@ -173,8 +179,9 @@ export const VoidChatArea: React.FC = ({
showModelDropdown = true,
featureName,
showSelections = false,
- selections = [],
- onSelectionsChange,
+ showProspectiveSelections = true,
+ staging,
+ setStaging,
}) => {
return (
= ({
}}
>
{/* Selections section */}
- {showSelections && onSelectionsChange && (
+ {showSelections && staging && setStaging && (
setStaging({ ...staging, selections })}
+ showProspectiveSelections={showProspectiveSelections}
/>
)}
@@ -535,19 +543,55 @@ export const SelectedFiles = (
type ChatBubbleMode = 'display' | 'edit'
-const ChatBubble = ({ chatMessage, isLoading }: { chatMessage: ChatMessage, isLoading?: boolean, }) => {
+const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatMessage, messageIdx?: number, isLoading?: boolean, }) => {
const role = chatMessage.role
+ const accessor = useAccessor()
+ const chatThreadsService = accessor.get('IChatThreadService')
+
// edit mode state
- const [mode, setMode] = useState('display')
- const [editText, setEditText] = useState(chatMessage.displayContent ?? '')
+ const [staging, setStaging] = chatThreadsService._useFocusedStagingState(messageIdx)
+ const mode: ChatBubbleMode = staging.isBeingEdited ? 'edit' : 'display'
+ const [isFocused, setIsFocused] = useState(false)
const [isHovered, setIsHovered] = useState(false)
+ const [isDisabled, setIsDisabled] = useState(false)
+ const [textAreaRefState, setTextAreaRef] = useState(null)
+ const textAreaFnsRef = useRef(null)
+ // initialize on first render, and when edit was just enabled
+ const _mustInitialize = useRef(true)
+ const _justEnabledEdit = useRef(false)
+ useEffect(() => {
+ const canInitialize = role === 'user' && mode === 'edit' && textAreaRefState
+ const shouldInitialize = _justEnabledEdit.current || _mustInitialize.current
+ if (canInitialize && shouldInitialize) {
+ setStaging({
+ ...staging,
+ selections: chatMessage.selections || [],
+ })
+ if (textAreaFnsRef.current)
+ textAreaFnsRef.current.setValue(chatMessage.displayContent || '')
- if (!chatMessage.content && !isLoading) { // don't show if empty and not loading (if loading, want to show)
- return null
+ textAreaRefState.focus();
+
+ _justEnabledEdit.current = false
+ _mustInitialize.current = false
+ }
+
+ }, [role, mode, _justEnabledEdit, textAreaRefState, textAreaFnsRef.current, _justEnabledEdit.current, _mustInitialize.current])
+ const EditSymbol = mode === 'display' ? Pencil : X
+ const onOpenEdit = () => {
+ setStaging({ ...staging, isBeingEdited: true })
+ chatThreadsService.setFocusedMessageIdx(messageIdx)
+ _justEnabledEdit.current = true
}
+ const onCloseEdit = () => {
+ setIsFocused(false)
+ setIsHovered(false)
+ setStaging({ ...staging, isBeingEdited: false })
+ chatThreadsService.setFocusedMessageIdx(undefined)
+ }
// set chat bubble contents
let chatbubbleContents: React.ReactNode
if (role === 'user') {
@@ -558,23 +602,85 @@ const ChatBubble = ({ chatMessage, isLoading }: { chatMessage: ChatMessage, isLo
>
}
else if (mode === 'edit') {
+
+ const onSubmit = async () => {
+
+ if (isDisabled) return;
+ if (!textAreaRefState) return;
+ if (messageIdx === undefined) return;
+
+ // cancel any streams on this thread
+ const thread = chatThreadsService.getCurrentThread()
+ chatThreadsService.cancelStreaming(thread.id)
+
+ // reset state
+ setStaging({ ...staging, isBeingEdited: false })
+ chatThreadsService.setFocusedMessageIdx(undefined)
+
+ // stream the edit
+ const userMessage = textAreaRefState.value;
+ await chatThreadsService.editUserMessageAndStreamResponse(userMessage, messageIdx)
+ }
+
+ const onAbort = () => {
+ const threadId = chatThreadsService.state.currentThreadId
+ chatThreadsService.cancelStreaming(threadId)
+ }
+
+ const onKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ onCloseEdit()
+ }
+ if (e.key === 'Enter' && !e.shiftKey) {
+ onSubmit()
+ }
+ }
+
+ if (!chatMessage.content && !isLoading) { // don't show if empty and not loading (if loading, want to show)
+ return null
+ }
+
chatbubbleContents = <>
-
-
}
@@ -628,6 +743,7 @@ export const SidebarChat = () => {
const accessor = useAccessor()
// const modelService = accessor.get('IModelService')
const commandService = accessor.get('ICommandService')
+ const chatThreadsService = accessor.get('IChatThreadService')
const settingsState = useSettingsState()
// ----- HIGHER STATE -----
@@ -636,8 +752,8 @@ export const SidebarChat = () => {
useEffect(() => {
const disposables: IDisposable[] = []
disposables.push(
- sidebarStateService.onDidFocusChat(() => { textAreaRef.current?.focus() }),
- sidebarStateService.onDidBlurChat(() => { textAreaRef.current?.blur() })
+ sidebarStateService.onDidFocusChat(() => { !chatThreadsService.isFocusingMessage() && textAreaRef.current?.focus() }),
+ sidebarStateService.onDidBlurChat(() => { !chatThreadsService.isFocusingMessage() && textAreaRef.current?.blur() })
)
return () => disposables.forEach(d => d.dispose())
}, [sidebarStateService, textAreaRef])
@@ -646,11 +762,10 @@ export const SidebarChat = () => {
// threads state
const chatThreadsState = useChatThreadsState()
- const chatThreadsService = accessor.get('IChatThreadService')
const currentThread = chatThreadsService.getCurrentThread()
const previousMessages = currentThread?.messages ?? []
- const selections = chatThreadsState.currentStagingSelections
+ const [staging, setStaging] = chatThreadsService._useFocusedStagingState()
// stream state
const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)
@@ -675,8 +790,6 @@ export const SidebarChat = () => {
const onSubmit = useCallback(async () => {
- console.log('onSubmit')
-
if (isDisabled) return
if (isStreaming) return
@@ -684,11 +797,11 @@ export const SidebarChat = () => {
const userMessage = textAreaRef.current?.value ?? ''
await chatThreadsService.addUserMessageAndStreamResponse(userMessage)
- chatThreadsService.setStaging([]) // clear staging
+ setStaging({ ...staging, selections: [], }) // clear staging
textAreaFnsRef.current?.setValue('')
textAreaRef.current?.focus() // focus input after submit
- }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef])
+ }, [chatThreadsService, isDisabled, isStreaming, textAreaRef, textAreaFnsRef, staging, setStaging])
const onAbort = () => {
const threadId = currentThread.id
@@ -709,7 +822,7 @@ export const SidebarChat = () => {
const prevMessagesHTML = useMemo(() => {
return previousMessages.map((message, i) =>
-
+
)
}, [previousMessages])
@@ -773,8 +886,9 @@ export const SidebarChat = () => {
isStreaming={isStreaming}
isDisabled={isDisabled}
showSelections={true}
- selections={selections || []}
- onSelectionsChange={chatThreadsService.setStaging.bind(chatThreadsService)}
+ showProspectiveSelections={prevMessagesHTML.length === 0}
+ staging={staging}
+ setStaging={setStaging}
onClickAnywhere={() => { textAreaRef.current?.focus() }}
featureName="Ctrl+L"
>
@@ -783,6 +897,7 @@ export const SidebarChat = () => {
placeholder={`${keybindingString ? `${keybindingString} to select. ` : ''}Enter instructions...`}
onChangeText={onChangeText}
onKeyDown={onKeyDown}
+ onFocus={() => { chatThreadsService.setFocusedMessageIdx(undefined) }}
ref={textAreaRef}
fnsRef={textAreaFnsRef}
multiline={true}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx
index 11c3a082..e62e7a9e 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx
@@ -58,9 +58,11 @@ type InputBox2Props = {
className?: string;
onChangeText?: (value: string) => void;
onKeyDown?: (e: React.KeyboardEvent) => void;
+ onFocus?: (e: React.FocusEvent) => void;
+ onBlur?: (e: React.FocusEvent) => void;
onChangeHeight?: (newHeight: number) => void;
}
-export const VoidInputBox2 = forwardRef(function X({ initValue, placeholder, multiline, fnsRef, className, onKeyDown, onChangeText }, ref) {
+export const VoidInputBox2 = forwardRef(function X({ initValue, placeholder, multiline, fnsRef, className, onKeyDown, onFocus, onBlur, onChangeText }, ref) {
// mirrors whatever is in ref
const textAreaRef = useRef(null)
@@ -114,6 +116,9 @@ export const VoidInputBox2 = forwardRef(fun
adjustHeight()
}, [fnsRef, fns, setEnabled, adjustHeight, ref])}
+ onFocus={onFocus}
+ onBlur={onBlur}
+
disabled={!isEnabled}
className={`w-full resize-none max-h-[500px] overflow-y-auto text-void-fg-1 placeholder:text-void-fg-3 ${className}`}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
index b15cd2a6..52af76f2 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
@@ -5,14 +5,14 @@
import React, { useState, useEffect } from 'react'
import { ThreadStreamState, ThreadsState } from '../../../chatThreadService.js'
-import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
+import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
import { VoidSidebarState } from '../../../sidebarStateService.js'
-import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js'
+import { VoidSettingsState } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
import { VoidUriState } from '../../../voidUriStateService.js';
import { VoidQuickEditState } from '../../../quickEditStateService.js'
-import { RefreshModelStateOfProvider } from '../../../../../../../platform/void/common/refreshModelService.js'
+import { RefreshModelStateOfProvider } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'
@@ -25,9 +25,9 @@ import { IContextViewService, IContextMenuService } from '../../../../../../../p
import { IFileService } from '../../../../../../../platform/files/common/files.js';
import { IHoverService } from '../../../../../../../platform/hover/browser/hover.js';
import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js';
-import { ILLMMessageService } from '../../../../../../../platform/void/common/llmMessageService.js';
-import { IRefreshModelService } from '../../../../../../../platform/void/common/refreshModelService.js';
-import { IVoidSettingsService } from '../../../../../../../platform/void/common/voidSettingsService.js';
+import { ILLMMessageService } from '../../../../../../../workbench/contrib/void/common/llmMessageService.js';
+import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js';
+import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js';
import { IInlineDiffsService } from '../../../inlineDiffsService.js';
import { IVoidUriStateService } from '../../../voidUriStateService.js';
import { IQuickEditStateService } from '../../../quickEditStateService.js';
@@ -46,7 +46,7 @@ import { IKeybindingService } from '../../../../../../../platform/keybinding/com
import { IEnvironmentService } from '../../../../../../../platform/environment/common/environment.js'
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'
import { IPathService } from '../../../../../../../workbench/services/path/common/pathService.js'
-import { IMetricsService } from '../../../../../../../platform/void/common/metricsService.js'
+import { IMetricsService } from '../../../../../../../workbench/contrib/void/common/metricsService.js'
@@ -288,6 +288,17 @@ export const useChatThreadsState = () => {
return () => { chatThreadsStateListeners.delete(ss) }
}, [ss])
return s
+ // allow user to set state natively in react
+ // const ss: React.Dispatch> = (action)=>{
+ // _ss(action)
+ // if (typeof action === 'function') {
+ // const newState = action(chatThreadsState)
+ // chatThreadsState = newState
+ // } else {
+ // chatThreadsState = action
+ // }
+ // }
+ // return [s, ss] as const
}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx
index 8016b4b8..2931b671 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/ModelDropdown.tsx
@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------*/
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { FeatureName, featureNames, isFeatureNameDisabled, ModelSelection, modelSelectionsEqual, ProviderName, providerNames, SettingsOfProvider } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
+import { FeatureName, featureNames, isFeatureNameDisabled, ModelSelection, modelSelectionsEqual, ProviderName, providerNames, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'
import { useSettingsState, useRefreshModelState, useAccessor } from '../util/services.js'
import { _VoidSelectBox, VoidCustomDropdownBox } from '../util/inputs.js'
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
import { IconWarning } from '../sidebar-tsx/SidebarChat.js'
import { VOID_OPEN_SETTINGS_ACTION_ID, VOID_TOGGLE_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'
-import { ModelOption } from '../../../../../../../platform/void/common/voidSettingsService.js'
+import { ModelOption } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
import { WarningBox } from './WarningBox.js'
const optionsEqual = (m1: ModelOption[], m2: ModelOption[]) => {
diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
index 0cd796e4..62511563 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx
@@ -5,7 +5,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
-import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
+import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled } from '../../../../common/voidSettingsTypes.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { VoidButton, VoidCheckBox, VoidCustomDropdownBox, VoidInputBox, VoidInputBox2, VoidSwitch } from '../util/inputs.js'
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
diff --git a/src/vs/workbench/contrib/void/browser/searchAndReplaceService.ts b/src/vs/workbench/contrib/void/browser/searchAndReplaceService.ts
new file mode 100644
index 00000000..c668ae26
--- /dev/null
+++ b/src/vs/workbench/contrib/void/browser/searchAndReplaceService.ts
@@ -0,0 +1,71 @@
+/*--------------------------------------------------------------------------------------
+ * Copyright 2025 Glass Devtools, Inc. All rights reserved.
+ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
+ *--------------------------------------------------------------------------------------*/
+
+import { Emitter, Event } from '../../../../base/common/event.js';
+import { Disposable } from '../../../../base/common/lifecycle.js';
+import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
+
+
+
+export type ChatMessageLocation = {
+ threadId: string;
+ messageIdx: number;
+}
+
+
+export type SearchAndReplaceBlock = {
+ search: string;
+ replace: string;
+}
+
+// service that manages state
+export type ApplyState = {
+ [applyBoxId: string]: {
+ searchAndReplaceBlocks: SearchAndReplaceBlock;
+ }
+}
+
+// the purpose of this service is to generate search and replace blocks for a given codeblock `codeblockId` and on a file `fileName` and version `fileVersion`
+
+export interface IFastApplyService {
+ readonly _serviceBrand: undefined;
+
+ // readonly state: ApplyState; // readonly to the user
+ // setState(newState: Partial): void;
+ // onDidChangeState: Event;
+}
+
+export const IVoidFastApplyService = createDecorator('voidFastApplyService');
+class VoidFastApplyService extends Disposable implements IFastApplyService {
+ _serviceBrand: undefined;
+
+ static readonly ID = 'voidFastApplyService';
+
+ private readonly _onDidChangeState = new Emitter();
+ readonly onDidChangeState: Event = this._onDidChangeState.event;
+
+
+ // state
+ // state: ApplyState
+
+ constructor(
+ ) {
+ super()
+
+ // initial state
+ // this.state = { currentUri: undefined }
+ }
+
+ setState(newState: Partial) {
+
+ // this.state = { ...this.state, ...newState }
+ this._onDidChangeState.fire()
+ }
+
+
+}
+
+registerSingleton(IVoidFastApplyService, VoidFastApplyService, InstantiationType.Eager);
diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts
index 5e085e8e..d65c51a7 100644
--- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts
+++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts
@@ -17,7 +17,7 @@ import { ICodeEditorService } from '../../../../editor/browser/services/codeEdit
import { IRange } from '../../../../editor/common/core/range.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { VOID_VIEW_CONTAINER_ID, VOID_VIEW_ID } from './sidebarPane.js';
-import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
+import { IMetricsService } from '../common/metricsService.js';
import { ISidebarStateService } from './sidebarStateService.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { VOID_TOGGLE_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
@@ -67,6 +67,13 @@ const getContentInRange = (model: ITextModel, range: IRange | null) => {
}
+const findMatchingStagingIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem) => {
+ return currentSelections?.findIndex(s =>
+ s.fileURI.fsPath === newSelection.fileURI.fsPath
+ && s.range?.startLineNumber === newSelection.range?.startLineNumber
+ && s.range?.endLineNumber === newSelection.range?.endLineNumber
+ )
+}
const VOID_OPEN_SIDEBAR_ACTION_ID = 'void.sidebar.open'
registerAction2(class extends Action2 {
@@ -124,26 +131,26 @@ registerAction2(class extends Action2 {
range: selectionRange,
}
- // add selection to staging
+ // update the staging selections
const chatThreadService = accessor.get(IChatThreadService)
- const currentStaging = chatThreadService.state.currentStagingSelections
- const currentStagingEltIdx = currentStaging?.findIndex(s =>
- s.fileURI.fsPath === model.uri.fsPath
- && s.range?.startLineNumber === selection.range?.startLineNumber
- && s.range?.endLineNumber === selection.range?.endLineNumber
- )
- // if matches with existing selection, overwrite
- if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
- chatThreadService.setStaging([
- ...currentStaging!.slice(0, currentStagingEltIdx),
+ const focusedMessageIdx = chatThreadService.getFocusedMessageIdx()
+ const [staging, setStaging] = chatThreadService._useFocusedStagingState(focusedMessageIdx)
+ const selections = staging.selections || []
+ const setSelections = (s: StagingSelectionItem[]) => setStaging({ ...staging, selections: s })
+
+ // if matches with existing selection, overwrite (since text may change)
+ const matchingStagingEltIdx = findMatchingStagingIndex(selections, selection)
+ if (matchingStagingEltIdx !== undefined && matchingStagingEltIdx !== -1) {
+ setSelections([
+ ...selections!.slice(0, matchingStagingEltIdx),
selection,
- ...currentStaging!.slice(currentStagingEltIdx + 1, Infinity)
+ ...selections!.slice(matchingStagingEltIdx + 1, Infinity)
])
}
- // if no match, add
+ // if no match, add it
else {
- chatThreadService.setStaging([...(currentStaging ?? []), selection])
+ setSelections([...(selections ?? []), selection])
}
}
diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts
index 110a5a26..19d20201 100644
--- a/src/vs/workbench/contrib/void/browser/void.contribution.ts
+++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts
@@ -33,3 +33,26 @@ import './media/void.css'
// update (frontend part, also see platform/)
import './voidUpdateActions.js'
+
+
+
+
+// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
+
+// llmMessage
+import '../common/llmMessageService.js'
+
+// voidSettings
+import '../common/voidSettingsService.js'
+
+// refreshModel
+import '../common/refreshModelService.js'
+
+// metrics
+import '../common/metricsService.js'
+
+// updates
+import '../common/voidUpdateService.js'
+
+// tools
+import '../common/toolsService.js'
diff --git a/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts b/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts
index 04b263b4..9d161b4e 100644
--- a/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts
+++ b/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts
@@ -9,8 +9,8 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
import { localize2 } from '../../../../nls.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
-import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
-import { IVoidUpdateService } from '../../../../platform/void/common/voidUpdateService.js';
+import { IMetricsService } from '../common/metricsService.js';
+import { IVoidUpdateService } from '../common/voidUpdateService.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
diff --git a/src/vs/platform/void/common/llmMessageService.ts b/src/vs/workbench/contrib/void/common/llmMessageService.ts
similarity index 93%
rename from src/vs/platform/void/common/llmMessageService.ts
rename to src/vs/workbench/contrib/void/common/llmMessageService.ts
index 4718e6c2..314031d4 100644
--- a/src/vs/platform/void/common/llmMessageService.ts
+++ b/src/vs/workbench/contrib/void/common/llmMessageService.ts
@@ -4,13 +4,14 @@
*--------------------------------------------------------------------------------------*/
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, OpenaiCompatibleModelResponse, } from './llmMessageTypes.js';
-import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
-import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
-import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
-import { generateUuid } from '../../../base/common/uuid.js';
-import { createDecorator } from '../../instantiation/common/instantiation.js';
-import { Event } from '../../../base/common/event.js';
-import { Disposable } from '../../../base/common/lifecycle.js';
+
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
+import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
+import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
+import { generateUuid } from '../../../../base/common/uuid.js';
+import { Event } from '../../../../base/common/event.js';
+import { Disposable } from '../../../../base/common/lifecycle.js';
import { IVoidSettingsService } from './voidSettingsService.js';
import { displayInfoOfProviderName, isFeatureNameDisabled } from './voidSettingsTypes.js';
// import { INotificationService } from '../../notification/common/notification.js';
diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts
similarity index 100%
rename from src/vs/platform/void/common/llmMessageTypes.ts
rename to src/vs/workbench/contrib/void/common/llmMessageTypes.ts
diff --git a/src/vs/platform/void/common/metricsService.ts b/src/vs/workbench/contrib/void/common/metricsService.ts
similarity index 74%
rename from src/vs/platform/void/common/metricsService.ts
rename to src/vs/workbench/contrib/void/common/metricsService.ts
index a3aeb6a8..d6892dd8 100644
--- a/src/vs/platform/void/common/metricsService.ts
+++ b/src/vs/workbench/contrib/void/common/metricsService.ts
@@ -3,14 +3,14 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import { createDecorator } from '../../instantiation/common/instantiation.js';
-import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';
-import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
-import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
-import { Action2, registerAction2 } from '../../actions/common/actions.js';
-import { localize2 } from '../../../nls.js';
-import { ServicesAccessor } from '../../../editor/browser/editorExtensions.js';
-import { INotificationService } from '../../notification/common/notification.js';
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
+import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js';
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
+import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
+import { localize2 } from '../../../../nls.js';
+import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
+import { registerAction2, Action2 } from '../../../../platform/actions/common/actions.js';
+import { INotificationService } from '../../../../platform/notification/common/notification.js';
export interface IMetricsService {
readonly _serviceBrand: undefined;
diff --git a/src/vs/platform/void/common/refreshModelService.ts b/src/vs/workbench/contrib/void/common/refreshModelService.ts
similarity index 95%
rename from src/vs/platform/void/common/refreshModelService.ts
rename to src/vs/workbench/contrib/void/common/refreshModelService.ts
index 7ef6a068..ff61e8a8 100644
--- a/src/vs/platform/void/common/refreshModelService.ts
+++ b/src/vs/workbench/contrib/void/common/refreshModelService.ts
@@ -3,14 +3,14 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import { createDecorator } from '../../instantiation/common/instantiation.js';
-import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { IVoidSettingsService } from './voidSettingsService.js';
import { ILLMMessageService } from './llmMessageService.js';
-import { Emitter, Event } from '../../../base/common/event.js';
-import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
+import { Emitter, Event } from '../../../../base/common/event.js';
+import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
import { RefreshableProviderName, refreshableProviderNames, SettingsOfProvider } from './voidSettingsTypes.js';
import { OllamaModelResponse, OpenaiCompatibleModelResponse } from './llmMessageTypes.js';
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts
new file mode 100644
index 00000000..8ffd6b9b
--- /dev/null
+++ b/src/vs/workbench/contrib/void/common/toolsService.ts
@@ -0,0 +1,182 @@
+import { CancellationToken } from '../../../../base/common/cancellation.js'
+import { URI } from '../../../../base/common/uri.js'
+import { IFileService, IFileStat } from '../../../../platform/files/common/files.js'
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'
+import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'
+import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'
+import { VSReadFileRaw } from '../../../../workbench/contrib/void/browser/helpers/readFile.js'
+import { QueryBuilder } from '../../../../workbench/services/search/common/queryBuilder.js'
+import { ISearchService } from '../../../../workbench/services/search/common/search.js'
+
+
+// tool use for AI
+
+
+
+// we do this using Anthropic's style and convert to OpenAI style later
+export type InternalToolInfo = {
+ description: string,
+ params: {
+ [paramName: string]: { type: string, description: string | undefined } // name -> type
+ },
+ required: string[], // required paramNames
+}
+
+// helper
+const pagination = {
+ desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`,
+ param: { pageNumber: { type: 'number', description: 'The page number (optional, default is 1).' }, }
+} as const
+
+export const contextTools = {
+ read_file: {
+ description: 'Returns file contents of a given URI.',
+ params: {
+ uri: { type: 'string', description: undefined },
+ },
+ required: ['uri'],
+ },
+
+ list_dir: {
+ description: `Returns all file names and folder names in a given URI. ${pagination.desc}`,
+ params: {
+ uri: { type: 'string', description: undefined },
+ ...pagination.param
+ },
+ required: ['uri'],
+ },
+
+ pathname_search: {
+ description: `Returns all pathnames that match a given grep query. You should use this when looking for a file with a specific name or path. This does NOT search file content. ${pagination.desc}`,
+ params: {
+ query: { type: 'string', description: undefined },
+ ...pagination.param,
+ },
+ required: ['query']
+ },
+
+ search: {
+ description: `Returns all code excerpts containing the given string or grep query. This does NOT search pathname. As a follow-up, you may want to use read_file to view the full file contents of the results. ${pagination.desc}`,
+ params: {
+ query: { type: 'string', description: undefined },
+ ...pagination.param,
+ },
+ required: ['query'],
+ },
+
+ // semantic_search: {
+ // description: 'Searches files semantically for the given string query.',
+ // // RAG
+ // },
+
+} as const satisfies { [name: string]: InternalToolInfo }
+
+export type ContextToolName = keyof typeof contextTools
+type ContextToolParamNames = keyof typeof contextTools[T]['params']
+type ContextToolParams = { [paramName in ContextToolParamNames]: unknown }
+
+type AllContextToolCallFns = {
+ [ToolName in ContextToolName]: ((p: (ContextToolParams)) => Promise)
+}
+
+
+
+
+
+
+
+// TODO check to make sure in workspace
+// TODO check to make sure is not gitignored
+
+
+async function generateDirectoryTreeMd(fileService: IFileService, rootURI: URI): Promise {
+ let output = ''
+ function traverseChildren(children: IFileStat[], depth: number) {
+ const indentation = ' '.repeat(depth);
+ for (const child of children) {
+ output += `${indentation}- ${child.name}\n`;
+ traverseChildren(child.children ?? [], depth + 1);
+ }
+ }
+ const stat = await fileService.resolve(rootURI, { resolveMetadata: false });
+
+ // kickstart recursion
+ output += `${stat.name}\n`;
+ traverseChildren(stat.children ?? [], 1);
+
+ return output;
+}
+
+
+const validateURI = (uriStr: unknown) => {
+ if (typeof uriStr !== 'string') throw new Error('(uri was not a string)')
+ const uri = URI.file(uriStr)
+ return uri
+}
+
+export interface IToolService {
+ readonly _serviceBrand: undefined;
+ callContextTool: (toolName: T, params: ContextToolParams) => Promise
+}
+
+export const IToolService = createDecorator('ToolService');
+
+export class ToolService implements IToolService {
+
+ readonly _serviceBrand: undefined;
+
+ contextToolCallFns: AllContextToolCallFns
+
+ constructor(
+ @IFileService fileService: IFileService,
+ @IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
+ @ISearchService searchService: ISearchService,
+ @IInstantiationService instantiationService: IInstantiationService,
+ ) {
+
+
+ const queryBuilder = instantiationService.createInstance(QueryBuilder);
+
+ this.contextToolCallFns = {
+ read_file: async ({ uri: uriStr }) => {
+ const uri = validateURI(uriStr)
+ const fileContents = await VSReadFileRaw(fileService, uri)
+ return fileContents ?? '(could not read file)'
+ },
+ list_dir: async ({ uri: uriStr }) => {
+ const uri = validateURI(uriStr)
+ const treeStr = await generateDirectoryTreeMd(fileService, uri)
+ return treeStr
+ },
+ pathname_search: async ({ query: queryStr }) => {
+ if (typeof queryStr !== 'string') return '(Error: query was not a string)'
+ const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: queryStr, });
+
+ const data = await searchService.fileSearch(query, CancellationToken.None);
+ const str = data.results.map(({ resource, results }) => resource.fsPath).join('\n')
+ return str
+ },
+ search: async ({ query: queryStr }) => {
+ if (typeof queryStr !== 'string') return '(Error: query was not a string)'
+ const query = queryBuilder.text({ pattern: queryStr, }, workspaceContextService.getWorkspace().folders.map(f => f.uri));
+
+ const data = await searchService.textSearch(query, CancellationToken.None);
+ const str = data.results.map(({ resource, results }) => resource.fsPath).join('\n')
+ return str
+ },
+
+ }
+
+
+
+ }
+
+ callContextTool: IToolService['callContextTool'] = (toolName, params) => {
+ return this.contextToolCallFns[toolName](params)
+ }
+
+
+}
+
+registerSingleton(IToolService, ToolService, InstantiationType.Eager);
+
diff --git a/src/vs/platform/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts
similarity index 95%
rename from src/vs/platform/void/common/voidSettingsService.ts
rename to src/vs/workbench/contrib/void/common/voidSettingsService.ts
index e9c57aa2..4322eaf4 100644
--- a/src/vs/platform/void/common/voidSettingsService.ts
+++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts
@@ -3,13 +3,13 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import { Emitter, Event } from '../../../base/common/event.js';
-import { Disposable } from '../../../base/common/lifecycle.js';
-import { deepClone } from '../../../base/common/objects.js';
-import { IEncryptionService } from '../../encryption/common/encryptionService.js';
-import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js';
-import { createDecorator } from '../../instantiation/common/instantiation.js';
-import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
+import { Emitter, Event } from '../../../../base/common/event.js';
+import { Disposable } from '../../../../base/common/lifecycle.js';
+import { deepClone } from '../../../../base/common/objects.js';
+import { IEncryptionService } from '../../../../platform/encryption/common/encryptionService.js';
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
+import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { IMetricsService } from './metricsService.js';
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultModelNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, displayInfoOfProviderName, defaultProviderSettings } from './voidSettingsTypes.js';
diff --git a/src/vs/platform/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts
similarity index 99%
rename from src/vs/platform/void/common/voidSettingsTypes.ts
rename to src/vs/workbench/contrib/void/common/voidSettingsTypes.ts
index fed10807..0a5bdc64 100644
--- a/src/vs/platform/void/common/voidSettingsTypes.ts
+++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts
@@ -227,7 +227,7 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
}
else if (providerName === 'deepseek') {
return {
- title: 'DeepSeek',
+ title: 'DeepSeek.com API',
}
}
else if (providerName === 'openRouter') {
@@ -248,17 +248,17 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
}
else if (providerName === 'gemini') {
return {
- title: 'Gemini',
+ title: 'Gemini API',
}
}
else if (providerName === 'groq') {
return {
- title: 'Groq',
+ title: 'Groq.com API',
}
}
else if (providerName === 'mistral') {
return {
- title: 'Mistral',
+ title: 'Mistral API',
}
}
diff --git a/src/vs/platform/void/common/voidUpdateService.ts b/src/vs/workbench/contrib/void/common/voidUpdateService.ts
similarity index 78%
rename from src/vs/platform/void/common/voidUpdateService.ts
rename to src/vs/workbench/contrib/void/common/voidUpdateService.ts
index fd3467dd..f552594f 100644
--- a/src/vs/platform/void/common/voidUpdateService.ts
+++ b/src/vs/workbench/contrib/void/common/voidUpdateService.ts
@@ -3,10 +3,10 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import { createDecorator } from '../../instantiation/common/instantiation.js';
-import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';
-import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
-import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
+import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js';
+import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
+import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
+import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
diff --git a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts
similarity index 84%
rename from src/vs/platform/void/electron-main/llmMessage/anthropic.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts
index ea220eed..91461b16 100644
--- a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts
+++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts
@@ -6,6 +6,27 @@
import Anthropic from '@anthropic-ai/sdk';
import { _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js';
import { anthropicMaxPossibleTokens } from '../../common/voidSettingsTypes.js';
+import { InternalToolInfo } from '../../common/toolsService.js';
+
+
+
+
+export const toAnthropicTool = (toolName: string, toolInfo: InternalToolInfo) => {
+ const { description, params, required } = toolInfo
+ return {
+ name: toolName,
+ description: description,
+ input_schema: {
+ type: 'object',
+ properties: params,
+ required: required,
+ }
+ } satisfies Anthropic.Messages.Tool
+}
+
+
+
+
export const sendAnthropicChat: _InternalSendLLMChatMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => {
diff --git a/src/vs/platform/void/electron-main/llmMessage/gemini.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/gemini.ts
similarity index 100%
rename from src/vs/platform/void/electron-main/llmMessage/gemini.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/gemini.ts
diff --git a/src/vs/platform/void/electron-main/llmMessage/groq.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts
similarity index 93%
rename from src/vs/platform/void/electron-main/llmMessage/groq.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts
index b8c29981..8f7efd14 100644
--- a/src/vs/platform/void/electron-main/llmMessage/groq.ts
+++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts
@@ -22,8 +22,6 @@ export const sendGroqChat: _InternalSendLLMChatMessageFnType = async ({ messages
messages: messages,
model: modelName,
stream: true,
- // temperature: 0.7,
- // max_tokens: parseMaxTokensStr(thisConfig.maxTokens),
})
.then(async response => {
_setAborter(() => response.controller.abort())
diff --git a/src/vs/platform/void/electron-main/llmMessage/mistral.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts
similarity index 97%
rename from src/vs/platform/void/electron-main/llmMessage/mistral.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts
index 8fddaf2e..cfddc2a5 100644
--- a/src/vs/platform/void/electron-main/llmMessage/mistral.ts
+++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts
@@ -21,8 +21,6 @@ export const sendMistralChat: _InternalSendLLMChatMessageFnType = async ({ messa
messages: messages,
model: modelName,
stream: true,
- // temperature: 0.7,
- // maxTokens: 2048,
})
.then(async response => {
// Mistral has a really nonstandard API - no interrupt and weird stream types
diff --git a/src/vs/platform/void/electron-main/llmMessage/ollama.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/ollama.ts
similarity index 100%
rename from src/vs/platform/void/electron-main/llmMessage/ollama.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/ollama.ts
diff --git a/src/vs/platform/void/electron-main/llmMessage/openai.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts
similarity index 75%
rename from src/vs/platform/void/electron-main/llmMessage/openai.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts
index 268eadfb..df4d2322 100644
--- a/src/vs/platform/void/electron-main/llmMessage/openai.ts
+++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts
@@ -6,43 +6,34 @@
import OpenAI from 'openai';
import { _InternalModelListFnType, _InternalSendLLMFIMMessageFnType, _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js';
import { Model } from 'openai/resources/models.js';
+import { InternalToolInfo } from '../../common/toolsService.js';
// import { parseMaxTokensStr } from './util.js';
+// developer command - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
+// prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
-export const openaiCompatibleList: _InternalModelListFnType = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => {
- const onSuccess = ({ models }: { models: Model[] }) => {
- onSuccess_({ models })
- }
- const onError = ({ error }: { error: string }) => {
- onError_({ error })
- }
-
- try {
- const thisConfig = settingsOfProvider.openAICompatible
- const openai = new OpenAI({ baseURL: thisConfig.endpoint, apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true })
-
- openai.models.list()
- .then(async (response) => {
- const models: Model[] = []
- models.push(...response.data)
- while (response.hasNextPage()) {
- models.push(...(await response.getNextPage()).data)
- }
- onSuccess({ models })
- })
- .catch((error) => {
- onError({ error: error + '' })
- })
- }
- catch (error) {
- onError({ error: error + '' })
- }
+export const toOpenAITool = (toolName: string, toolInfo: InternalToolInfo) => {
+ const { description, params, required } = toolInfo
+ return {
+ type: 'function',
+ function: {
+ name: toolName,
+ description: description,
+ parameters: {
+ type: 'object',
+ properties: params,
+ required: required,
+ }
+ }
+ } satisfies OpenAI.Chat.Completions.ChatCompletionTool
}
+
+
type NewParams = Pick[0] & Parameters<_InternalSendLLMFIMMessageFnType>[0], 'settingsOfProvider' | 'providerName'>
const newOpenAI = ({ settingsOfProvider, providerName }: NewParams) => {
@@ -81,26 +72,46 @@ const newOpenAI = ({ settingsOfProvider, providerName }: NewParams) => {
+// might not currently be used in the code
+export const openaiCompatibleList: _InternalModelListFnType = async ({ onSuccess: onSuccess_, onError: onError_, settingsOfProvider }) => {
+ const onSuccess = ({ models }: { models: Model[] }) => {
+ onSuccess_({ models })
+ }
+
+ const onError = ({ error }: { error: string }) => {
+ onError_({ error })
+ }
+
+ try {
+ const openai = newOpenAI({ providerName: 'openAICompatible', settingsOfProvider })
+
+ openai.models.list()
+ .then(async (response) => {
+ const models: Model[] = []
+ models.push(...response.data)
+ while (response.hasNextPage()) {
+ models.push(...(await response.getNextPage()).data)
+ }
+ onSuccess({ models })
+ })
+ .catch((error) => {
+ onError({ error: error + '' })
+ })
+ }
+ catch (error) {
+ onError({ error: error + '' })
+ }
+}
+
+
+
+
export const sendOpenAIFIM: _InternalSendLLMFIMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }) => {
- const openai: OpenAI = newOpenAI({ providerName, settingsOfProvider })
+ // openai.completions has a FIM parameter called `suffix`, but it's deprecated and only works for ~GPT 3 era models
- const options: OpenAI.Completions.CompletionCreateParamsStreaming = {
- prompt: messages.prefix,
- suffix: messages.suffix,
- model: modelName,
- stream: true,
- // max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)
- }
-
-
- openai.completions
- .create(options)
- .then(async response => {
- // TODO!!!
- console.log('RESPONSE', response)
- })
+ onFinalMessage({ fullText: 'TODO' })
}
@@ -116,7 +127,7 @@ export const sendOpenAIChat: _InternalSendLLMChatMessageFnType = ({ messages, on
model: modelName,
messages: messages,
stream: true,
- // max_completion_tokens: parseMaxTokensStr(thisConfig.maxTokens)
+ // tools: Object.keys(contextTools).map(name => toOpenAITool(name, contextTools[name as ContextToolName])),
}
openai.chat.completions
@@ -125,7 +136,11 @@ export const sendOpenAIChat: _InternalSendLLMChatMessageFnType = ({ messages, on
_setAborter(() => response.controller.abort())
// when receive text
for await (const chunk of response) {
- const newText = chunk.choices[0]?.delta?.content || '';
+
+ let newText = ''
+ newText += chunk.choices[0]?.delta?.tool_calls?.[0]?.function?.name ?? ''
+ newText += chunk.choices[0]?.delta?.tool_calls?.[0]?.function?.arguments ?? ''
+ newText += chunk.choices[0]?.delta?.content ?? ''
fullText += newText;
onText({ newText, fullText });
}
diff --git a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts
similarity index 72%
rename from src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts
index 7cea9d5a..dc70c36c 100644
--- a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts
+++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts
@@ -8,7 +8,7 @@ import { IMetricsService } from '../../common/metricsService.js';
import { sendAnthropicChat } from './anthropic.js';
import { sendOllamaFIM, sendOllamaChat } from './ollama.js';
-import { sendOpenAIChat } from './openai.js';
+import { sendOpenAIChat, sendOpenAIFIM } from './openai.js';
import { sendGeminiChat } from './gemini.js';
import { sendGroqChat } from './groq.js';
import { sendMistralChat } from './mistral.js';
@@ -65,9 +65,14 @@ export const sendLLMMessage = ({
metricsService: IMetricsService
) => {
- // messages.unshift({ role: 'system', content: aiInstructions })
- const messagesArr = messagesType === 'chatMessages' ? cleanChatMessages(messages_) : []
+ let messagesArr: _InternalLLMChatMessage[] = []
+ if (messagesType === 'chatMessages') {
+ messagesArr = cleanChatMessages([
+ { role: 'system', content: aiInstructions },
+ ...messages_
+ ])
+ }
// only captures number of messages and message "shape", no actual code, instructions, prompts, etc
const captureLLMEvent = (eventId: string, extras?: object) => {
@@ -131,33 +136,32 @@ export const sendLLMMessage = ({
try {
switch (providerName) {
- case 'anthropic':
- sendAnthropicChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
- break;
case 'openAI':
case 'openRouter':
case 'deepseek':
case 'openAICompatible':
- sendOpenAIChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
- break;
- case 'gemini':
- sendGeminiChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
+ if (messagesType === 'FIMMessage') sendOpenAIFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
+ else /* */ sendOpenAIChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'ollama':
- if ( // TODO @andrew in future we want to use our own templates instead of using ollamaFIM
- messagesType === 'FIMMessage'
- && settingsOfProvider['ollama']._didFillInProviderSettings
- && settingsOfProvider['ollama'].models.some(m => !m.isHidden)
- )
- sendOllamaFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName })
- else
- sendOllamaChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
+ if (messagesType === 'FIMMessage') sendOllamaFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName })
+ else /* */ sendOllamaChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName })
+ break;
+ case 'anthropic':
+ if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Anthropic FIM' })
+ else /* */ sendAnthropicChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
+ break;
+ case 'gemini':
+ if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Gemini FIM' })
+ else /* */ sendGeminiChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'groq':
- sendGroqChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
+ if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Groq FIM' })
+ else /* */ sendGroqChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
case 'mistral':
- sendMistralChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
+ if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Mistral FIM' })
+ else /* */ sendMistralChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName });
break;
default:
onError({ message: `Error: Void provider was "${providerName}", which is not recognized.`, fullError: null })
diff --git a/src/vs/platform/void/electron-main/llmMessageChannel.ts b/src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts
similarity index 95%
rename from src/vs/platform/void/electron-main/llmMessageChannel.ts
rename to src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts
index 2c44e2ec..98725631 100644
--- a/src/vs/platform/void/electron-main/llmMessageChannel.ts
+++ b/src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts
@@ -6,8 +6,8 @@
// registered in app.ts
// code convention is to make a service responsible for this stuff, and not a channel, but having fewer files is simpler...
-import { IServerChannel } from '../../../base/parts/ipc/common/ipc.js';
-import { Emitter, Event } from '../../../base/common/event.js';
+import { IServerChannel } from '../../../../base/parts/ipc/common/ipc.js';
+import { Emitter, Event } from '../../../../base/common/event.js';
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainSendLLMMessageParams, AbortRef, SendLLMMessageParams, MainLLMMessageAbortParams, MainModelListParams, ModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, OllamaModelResponse, OpenaiCompatibleModelResponse, } from '../common/llmMessageTypes.js';
import { sendLLMMessage } from './llmMessage/sendLLMMessage.js'
import { IMetricsService } from '../common/metricsService.js';
@@ -66,7 +66,7 @@ export class LLMMessageChannel implements IServerChannel {
}
}
- // browser uses this to call
+ // browser uses this to call (see this.channel.call() in llmMessageService.ts for all usages)
async call(_: unknown, command: string, params: any): Promise {
try {
if (command === 'sendLLMMessage') {
diff --git a/src/vs/platform/void/electron-main/metricsMainService.ts b/src/vs/workbench/contrib/void/electron-main/metricsMainService.ts
similarity index 86%
rename from src/vs/platform/void/electron-main/metricsMainService.ts
rename to src/vs/workbench/contrib/void/electron-main/metricsMainService.ts
index cd8abf2b..592f79c4 100644
--- a/src/vs/platform/void/electron-main/metricsMainService.ts
+++ b/src/vs/workbench/contrib/void/electron-main/metricsMainService.ts
@@ -3,14 +3,13 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import { Disposable } from '../../../base/common/lifecycle.js';
-import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js';
-import { generateUuid } from '../../../base/common/uuid.js';
-import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
-
-import { IProductService } from '../../product/common/productService.js';
-import { StorageScope, StorageTarget } from '../../storage/common/storage.js';
-import { IApplicationStorageMainService } from '../../storage/electron-main/storageMainService.js';
+import { Disposable } from '../../../../base/common/lifecycle.js';
+import { isLinux, isMacintosh, isWindows } from '../../../../base/common/platform.js';
+import { generateUuid } from '../../../../base/common/uuid.js';
+import { IEnvironmentMainService } from '../../../../platform/environment/electron-main/environmentMainService.js';
+import { IProductService } from '../../../../platform/product/common/productService.js';
+import { StorageTarget, StorageScope } from '../../../../platform/storage/common/storage.js';
+import { IApplicationStorageMainService } from '../../../../platform/storage/electron-main/storageMainService.js';
import { IMetricsService } from '../common/metricsService.js';
import { PostHog } from 'posthog-node'
diff --git a/src/vs/workbench/contrib/void/electron-main/templates/templates.ts b/src/vs/workbench/contrib/void/electron-main/templates/templates.ts
new file mode 100644
index 00000000..138f7be3
--- /dev/null
+++ b/src/vs/workbench/contrib/void/electron-main/templates/templates.ts
@@ -0,0 +1,13 @@
+/*
+
+modelName -> {
+ system_message_type: 'system' | 'developer' (openai) | null // if null, we will just do a string of system message
+ supports_tools: boolean // we will just do a string of tool use if it doesn't support
+ supports_autocomplete_FIM (suffix) // we will just do a description of FIM if it doens't support <|fim_hole|>
+
+ supports_streaming: boolean // (o1 does NOT) we will just dump the final result if doesn't support it
+ max_tokens: number // required, DEFAULT is Infinity
+
+}
+
+*/
diff --git a/src/vs/platform/void/electron-main/voidUpdateMainService.ts b/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts
similarity index 82%
rename from src/vs/platform/void/electron-main/voidUpdateMainService.ts
rename to src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts
index 029db5f4..c691ff56 100644
--- a/src/vs/platform/void/electron-main/voidUpdateMainService.ts
+++ b/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts
@@ -3,10 +3,9 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
-import { Disposable } from '../../../base/common/lifecycle.js';
-import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
-
-import { IProductService } from '../../product/common/productService.js';
+import { Disposable } from '../../../../base/common/lifecycle.js';
+import { IEnvironmentMainService } from '../../../../platform/environment/electron-main/environmentMainService.js';
+import { IProductService } from '../../../../platform/product/common/productService.js';
import { IVoidUpdateService } from '../common/voidUpdateService.js';
diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts
index 14f3fec1..394da127 100644
--- a/src/vs/workbench/workbench.common.main.ts
+++ b/src/vs/workbench/workbench.common.main.ts
@@ -17,7 +17,6 @@ import './browser/workbench.contribution.js';
//#region --- Void
// Void added this:
import './contrib/void/browser/void.contribution.js';
-import '../platform/void/browser/void.contribution.js';
//#endregion