mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Merge pull request #400 from voideditor/model-selection
GPT 4.1 and Claude 3.7 Thinking with Tools
This commit is contained in:
commit
eda8517c78
21 changed files with 1072 additions and 921 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"nameShort": "Void",
|
"nameShort": "Void",
|
||||||
"nameLong": "Void",
|
"nameLong": "Void",
|
||||||
"voidVersion": "1.2.0",
|
"voidVersion": "1.2.1",
|
||||||
"applicationName": "void",
|
"applicationName": "void",
|
||||||
"dataFolderName": ".void-editor",
|
"dataFolderName": ".void-editor",
|
||||||
"win32MutexName": "voideditor",
|
"win32MutexName": "voideditor",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||||
import { isWindows } from '../../../../base/common/platform.js';
|
import { isWindows } from '../../../../base/common/platform.js';
|
||||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||||
import { FeatureName } from '../common/voidSettingsTypes.js';
|
import { FeatureName } from '../common/voidSettingsTypes.js';
|
||||||
|
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
||||||
// import { IContextGatheringService } from './contextGatheringService.js';
|
// import { IContextGatheringService } from './contextGatheringService.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -791,18 +792,21 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||||
const featureName: FeatureName = 'Autocomplete'
|
const featureName: FeatureName = 'Autocomplete'
|
||||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||||
|
const aiInstructions = this._settingsService.state.globalSettings.aiInstructions
|
||||||
|
|
||||||
// set parameters of `newAutocompletion` appropriately
|
// set parameters of `newAutocompletion` appropriately
|
||||||
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
|
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const requestId = this._llmMessageService.sendLLMMessage({
|
const requestId = this._llmMessageService.sendLLMMessage({
|
||||||
messagesType: 'FIMMessage',
|
messagesType: 'FIMMessage',
|
||||||
messages: {
|
messages: this._convertToLLMMessageService.prepareFIMMessage({
|
||||||
prefix: llmPrefix,
|
messages: {
|
||||||
suffix: llmSuffix,
|
prefix: llmPrefix,
|
||||||
stopTokens: stopTokens,
|
suffix: llmSuffix,
|
||||||
},
|
stopTokens: stopTokens,
|
||||||
|
},
|
||||||
|
aiInstructions
|
||||||
|
}),
|
||||||
modelSelection,
|
modelSelection,
|
||||||
modelSelectionOptions,
|
modelSelectionOptions,
|
||||||
logging: { loggingName: 'Autocomplete' },
|
logging: { loggingName: 'Autocomplete' },
|
||||||
|
|
@ -890,6 +894,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
||||||
@IEditorService private readonly _editorService: IEditorService,
|
@IEditorService private readonly _editorService: IEditorService,
|
||||||
@IModelService private readonly _modelService: IModelService,
|
@IModelService private readonly _modelService: IModelService,
|
||||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||||
|
@IConvertToLLMMessageService private readonly _convertToLLMMessageService: IConvertToLLMMessageService
|
||||||
// @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
|
// @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,10 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
||||||
import { URI } from '../../../../base/common/uri.js';
|
import { URI } from '../../../../base/common/uri.js';
|
||||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||||
import { chat_userMessageContent, chat_systemMessage, ToolName, toolCallXMLStr, } from '../common/prompt/prompts.js';
|
import { chat_userMessageContent, ToolName, } from '../common/prompt/prompts.js';
|
||||||
import { getErrorMessage, LLMChatMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
import { getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
|
||||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||||
import { ChatMode, FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
||||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||||
import { ToolCallParams, ToolResultType, toolNamesThatRequireApproval } from '../common/toolsServiceTypes.js';
|
import { ToolCallParams, ToolResultType, toolNamesThatRequireApproval } from '../common/toolsServiceTypes.js';
|
||||||
import { IToolsService } from './toolsService.js';
|
import { IToolsService } from './toolsService.js';
|
||||||
|
|
@ -23,7 +22,6 @@ import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||||
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
||||||
import { ChatMessage, CheckpointEntry, CodespanLocationLink, StagingSelectionItem, ToolMessage } from '../common/chatThreadServiceTypes.js';
|
import { ChatMessage, CheckpointEntry, CodespanLocationLink, StagingSelectionItem, ToolMessage } from '../common/chatThreadServiceTypes.js';
|
||||||
import { Position } from '../../../../editor/common/core/position.js';
|
import { Position } from '../../../../editor/common/core/position.js';
|
||||||
import { ITerminalToolService } from './terminalToolService.js';
|
|
||||||
import { IMetricsService } from '../common/metricsService.js';
|
import { IMetricsService } from '../common/metricsService.js';
|
||||||
import { shorten } from '../../../../base/common/labels.js';
|
import { shorten } from '../../../../base/common/labels.js';
|
||||||
import { IVoidModelService } from '../common/voidModelService.js';
|
import { IVoidModelService } from '../common/voidModelService.js';
|
||||||
|
|
@ -33,11 +31,35 @@ import { findLast, findLastIdx } from '../../../../base/common/arraysFind.js';
|
||||||
import { IEditCodeService } from './editCodeServiceInterface.js';
|
import { IEditCodeService } from './editCodeServiceInterface.js';
|
||||||
import { VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
|
import { VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
|
||||||
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
|
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
|
||||||
import { IModelService } from '../../../../editor/common/services/model.js';
|
|
||||||
import { IDirectoryStrService } from './directoryStrService.js';
|
|
||||||
import { truncate } from '../../../../base/common/strings.js';
|
import { truncate } from '../../../../base/common/strings.js';
|
||||||
import { THREAD_STORAGE_KEY } from '../common/storageKeys.js';
|
import { THREAD_STORAGE_KEY } from '../common/storageKeys.js';
|
||||||
import { deepClone } from '../../../../base/common/objects.js';
|
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
||||||
|
|
||||||
|
export const findStagingSelectionIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): number | null => {
|
||||||
|
if (!currentSelections) return null
|
||||||
|
|
||||||
|
for (let i = 0; i < currentSelections.length; i += 1) {
|
||||||
|
const s = currentSelections[i]
|
||||||
|
|
||||||
|
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
||||||
|
|
||||||
|
if (s.type === 'File' && newSelection.type === 'File') {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') {
|
||||||
|
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
||||||
|
// if there's any collision return true
|
||||||
|
const [oldStart, oldEnd] = s.range
|
||||||
|
const [newStart, newEnd] = newSelection.range
|
||||||
|
if (oldStart !== newStart || oldEnd !== newEnd) continue
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if (s.type === 'Folder' && newSelection.type === 'Folder') {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -221,17 +243,14 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
@IVoidModelService private readonly _voidModelService: IVoidModelService,
|
@IVoidModelService private readonly _voidModelService: IVoidModelService,
|
||||||
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
|
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
|
||||||
@IToolsService private readonly _toolsService: IToolsService,
|
@IToolsService private readonly _toolsService: IToolsService,
|
||||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
|
||||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||||
@ITerminalToolService private readonly _terminalToolService: ITerminalToolService,
|
|
||||||
@IMetricsService private readonly _metricsService: IMetricsService,
|
@IMetricsService private readonly _metricsService: IMetricsService,
|
||||||
@IEditorService private readonly _editorService: IEditorService,
|
@IEditorService private readonly _editorService: IEditorService,
|
||||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||||
@IEditCodeService private readonly _editCodeService: IEditCodeService,
|
@IEditCodeService private readonly _editCodeService: IEditCodeService,
|
||||||
@INotificationService private readonly _notificationService: INotificationService,
|
@INotificationService private readonly _notificationService: INotificationService,
|
||||||
@IModelService private readonly _modelService: IModelService,
|
@IConvertToLLMMessageService private readonly _convertToLLMMessagesService: IConvertToLLMMessageService,
|
||||||
@IDirectoryStrService private readonly _directoryStrService: IDirectoryStrService,
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
|
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
|
||||||
|
|
@ -288,14 +307,16 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldStagingSelections = this.getCurrentThreadState().stagingSelections || [];
|
const oldStagingSelections = this.getCurrentThreadState().stagingSelections || [];
|
||||||
const fileIsAlreadyHere = oldStagingSelections.some(s => s.type === 'File' && s.uri.fsPath === newStagingSelection.uri.fsPath)
|
|
||||||
if (fileIsAlreadyHere) return
|
|
||||||
|
|
||||||
// remove all old selectons that are marked as `wasAddedAsCurrentFile`, and add new selection
|
// remove all old selectons that are marked as `wasAddedAsCurrentFile`
|
||||||
const newStagingSelections: StagingSelectionItem[] = [
|
const newStagingSelections: StagingSelectionItem[] = oldStagingSelections.filter(s => s.state && !s.state.wasAddedAsCurrentFile)
|
||||||
...oldStagingSelections.filter(s => !s.state?.wasAddedAsCurrentFile),
|
|
||||||
newStagingSelection
|
const fileIsAlreadyHere = oldStagingSelections.some(s => s.type === 'File' && s.uri.fsPath === newStagingSelection.uri.fsPath)
|
||||||
]
|
|
||||||
|
if (!fileIsAlreadyHere) {
|
||||||
|
newStagingSelections.push(newStagingSelection)
|
||||||
|
}
|
||||||
|
|
||||||
this.setCurrentThreadState({ stagingSelections: newStagingSelections });
|
this.setCurrentThreadState({ stagingSelections: newStagingSelections });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -454,10 +475,10 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
}
|
}
|
||||||
else return
|
else return
|
||||||
|
|
||||||
const { name } = lastMsg
|
const { name, id, rawParams } = lastMsg
|
||||||
|
|
||||||
const errorMessage = this.errMsgs.rejected
|
const errorMessage = this.errMsgs.rejected
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams })
|
||||||
this._setStreamState(threadId, {}, 'set')
|
this._setStreamState(threadId, {}, 'set')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,7 +503,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
const llmCancelToken = this.streamState[threadId]?.streamingToken
|
const llmCancelToken = this.streamState[threadId]?.streamingToken
|
||||||
if (llmCancelToken !== undefined) { this._llmMessageService.abort(llmCancelToken) }
|
if (llmCancelToken !== undefined) { this._llmMessageService.abort(llmCancelToken) }
|
||||||
|
|
||||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, toolCall: toolCallSoFar, anthropicReasoning: null })
|
this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||||
|
|
||||||
if (toolCallSoFar) {
|
if (toolCallSoFar) {
|
||||||
this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name })
|
this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name })
|
||||||
|
|
@ -505,69 +526,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
private readonly _currentlyRunningToolInterruptor: { [threadId: string]: (() => void) | undefined } = {}
|
private readonly _currentlyRunningToolInterruptor: { [threadId: string]: (() => void) | undefined } = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// system message
|
|
||||||
private _generateSystemMessage = async (chatMode: ChatMode) => {
|
|
||||||
const workspaceFolders = this._workspaceContextService.getWorkspace().folders.map(f => f.uri.fsPath)
|
|
||||||
|
|
||||||
const openedURIs = this._modelService.getModels().filter(m => m.isAttachedToEditor()).map(m => m.uri.fsPath) || [];
|
|
||||||
const activeURI = this._editorService.activeEditor?.resource?.fsPath;
|
|
||||||
|
|
||||||
const directoryStr = await this._directoryStrService.getAllDirectoriesStr({
|
|
||||||
cutOffMessage: chatMode === 'agent' || chatMode === 'gather' ? `...Directories string cut off, use tools to read more...`
|
|
||||||
: `...Directories string cut off, ask user for more if necessary...`
|
|
||||||
})
|
|
||||||
|
|
||||||
const runningTerminalIds = this._terminalToolService.listTerminalIds()
|
|
||||||
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode })
|
|
||||||
return systemMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
private _generateLLMMessages = async (threadId: string) => {
|
|
||||||
const thread = this.state.allThreads[threadId]
|
|
||||||
if (!thread) return []
|
|
||||||
|
|
||||||
const chatMessages = deepClone(thread.messages)
|
|
||||||
const llmChatMessages: LLMChatMessage[] = []
|
|
||||||
|
|
||||||
// merge tools into user message
|
|
||||||
for (const c of chatMessages) {
|
|
||||||
if (c.role === 'assistant') {
|
|
||||||
// if called a tool, re-add its XML to the message
|
|
||||||
// alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere
|
|
||||||
let content = c.displayContent
|
|
||||||
if (c.toolCall) {
|
|
||||||
content = `${content}\n\n${toolCallXMLStr(c.toolCall)}`
|
|
||||||
}
|
|
||||||
llmChatMessages.push({ role: c.role, content: content, anthropicReasoning: c.anthropicReasoning })
|
|
||||||
}
|
|
||||||
else if (c.role === 'user' || c.role === 'tool') {
|
|
||||||
if (c.role === 'tool')
|
|
||||||
c.content = `<${c.name}_result>\n${c.content}\n</${c.name}_result>`
|
|
||||||
|
|
||||||
if (llmChatMessages.length === 0 || llmChatMessages[llmChatMessages.length - 1].role !== 'user')
|
|
||||||
llmChatMessages.push({ role: 'user', content: c.content })
|
|
||||||
else
|
|
||||||
llmChatMessages[llmChatMessages.length - 1].content += '\n\n' + c.content
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (c.role === 'interrupted_streaming_tool') { // pass
|
|
||||||
}
|
|
||||||
else if (c.role === 'checkpoint') { // pass
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error(`Role ${(c as any).role} not recognized.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return llmChatMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// returns true when the tool call is waiting for user approval
|
// returns true when the tool call is waiting for user approval
|
||||||
private _runToolCall = async (
|
private _runToolCall = async (
|
||||||
threadId: string,
|
threadId: string,
|
||||||
toolName: ToolName,
|
toolName: ToolName,
|
||||||
opts: { preapproved: true, validatedParams: ToolCallParams[ToolName] } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj },
|
toolId: string,
|
||||||
|
opts: { preapproved: true, unvalidatedToolParams: RawToolParamsObj, validatedParams: ToolCallParams[ToolName] } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj },
|
||||||
): Promise<{ awaitingUserApproval?: boolean, interrupted?: boolean }> => {
|
): Promise<{ awaitingUserApproval?: boolean, interrupted?: boolean }> => {
|
||||||
|
|
||||||
// compute these below
|
// compute these below
|
||||||
|
|
@ -582,7 +546,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
toolParams = params
|
toolParams = params
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error)
|
const errorMessage = getErrorMessage(error)
|
||||||
this._addMessageToThread(threadId, { role: 'tool', type: 'invalid_params', params: null, result: null, name: toolName, content: errorMessage, })
|
this._addMessageToThread(threadId, { role: 'tool', type: 'invalid_params', rawParams: opts.unvalidatedToolParams, result: null, name: toolName, content: errorMessage, id: toolId, })
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
// once validated, add checkpoint for edit
|
// once validated, add checkpoint for edit
|
||||||
|
|
@ -593,7 +557,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
if (toolRequiresApproval) {
|
if (toolRequiresApproval) {
|
||||||
const autoApprove = this._settingsService.state.globalSettings.autoApprove
|
const autoApprove = this._settingsService.state.globalSettings.autoApprove
|
||||||
// add a tool_request because we use it for UI if a tool is loading (this should be improved in the future)
|
// add a tool_request because we use it for UI if a tool is loading (this should be improved in the future)
|
||||||
this._addMessageToThread(threadId, { role: 'tool', type: 'tool_request', content: '(never)', result: null, name: toolName, params: toolParams })
|
this._addMessageToThread(threadId, { role: 'tool', type: 'tool_request', content: '(never)', result: null, name: toolName, params: toolParams, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||||
if (!autoApprove) {
|
if (!autoApprove) {
|
||||||
return { awaitingUserApproval: true }
|
return { awaitingUserApproval: true }
|
||||||
}
|
}
|
||||||
|
|
@ -603,9 +567,11 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
toolParams = opts.validatedParams
|
toolParams = opts.validatedParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 3. call the tool
|
// 3. call the tool
|
||||||
this._setStreamState(threadId, { isRunning: 'tool' }, 'merge')
|
this._setStreamState(threadId, { isRunning: 'tool' }, 'merge')
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||||
|
|
||||||
let interrupted = false
|
let interrupted = false
|
||||||
try {
|
try {
|
||||||
|
|
@ -624,7 +590,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
|
|
||||||
|
|
||||||
const errorMessage = getErrorMessage(error)
|
const errorMessage = getErrorMessage(error)
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -633,12 +599,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = this.errMsgs.errWhenStringifying(error)
|
const errorMessage = this.errMsgs.errWhenStringifying(error)
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. add to history and keep going
|
// 5. add to history and keep going
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'success', params: toolParams, result: toolResult, name: toolName, content: toolResultStr, })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'success', params: toolParams, result: toolResult, name: toolName, content: toolResultStr, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
};
|
};
|
||||||
|
|
@ -659,6 +625,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
callThisToolFirst?: ToolMessage<ToolName> & { type: 'tool_request' }
|
callThisToolFirst?: ToolMessage<ToolName> & { type: 'tool_request' }
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
|
||||||
// above just defines helpers, below starts the actual function
|
// above just defines helpers, below starts the actual function
|
||||||
const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here
|
const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here
|
||||||
|
|
||||||
|
|
@ -672,7 +639,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
|
|
||||||
// before enter loop, call tool
|
// before enter loop, call tool
|
||||||
if (callThisToolFirst) {
|
if (callThisToolFirst) {
|
||||||
const { interrupted } = await this._runToolCall(threadId, callThisToolFirst.name, { preapproved: true, validatedParams: callThisToolFirst.params })
|
const { interrupted } = await this._runToolCall(threadId, callThisToolFirst.name, callThisToolFirst.id, { preapproved: true, unvalidatedToolParams: callThisToolFirst.rawParams, validatedParams: callThisToolFirst.params })
|
||||||
if (interrupted) return
|
if (interrupted) return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -688,34 +655,36 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
|
|
||||||
// send llm message
|
// send llm message
|
||||||
this._setStreamState(threadId, { isRunning: 'LLM' }, 'merge')
|
this._setStreamState(threadId, { isRunning: 'LLM' }, 'merge')
|
||||||
const systemMessage = await this._generateSystemMessage(chatMode)
|
|
||||||
const llmMessages = await this._generateLLMMessages(threadId)
|
const chatMessages = this.state.allThreads[threadId]?.messages ?? []
|
||||||
const messages: LLMChatMessage[] = [
|
const { messages, separateSystemMessage } = await this._convertToLLMMessagesService.prepareLLMChatMessages({
|
||||||
{ role: 'system', content: systemMessage },
|
chatMessages,
|
||||||
...llmMessages
|
modelSelection,
|
||||||
]
|
chatMode
|
||||||
|
})
|
||||||
|
|
||||||
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
||||||
messagesType: 'chatMessages',
|
messagesType: 'chatMessages',
|
||||||
chatMode,
|
chatMode,
|
||||||
messages,
|
messages: messages,
|
||||||
modelSelection,
|
modelSelection,
|
||||||
modelSelectionOptions,
|
modelSelectionOptions,
|
||||||
logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } },
|
logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } },
|
||||||
|
separateSystemMessage: separateSystemMessage,
|
||||||
onText: ({ fullText, fullReasoning, toolCall }) => {
|
onText: ({ fullText, fullReasoning, toolCall }) => {
|
||||||
this._setStreamState(threadId, { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall }, 'merge')
|
this._setStreamState(threadId, { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall }, 'merge')
|
||||||
},
|
},
|
||||||
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
||||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, toolCall, anthropicReasoning })
|
this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||||
this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
||||||
resMessageIsDonePromise(toolCall) // resolve with tool calls
|
resMessageIsDonePromise(toolCall) // resolve with tool calls
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const messageSoFar = this.streamState[threadId]?.displayContentSoFar ?? ''
|
const messageSoFar = this.streamState[threadId]?.displayContentSoFar ?? ''
|
||||||
const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? ''
|
const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? ''
|
||||||
const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar
|
// const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar
|
||||||
// add assistant's message to chat history, and clear selection
|
// add assistant's message to chat history, and clear selection
|
||||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: messageSoFar, reasoning: reasoningSoFar, toolCall: toolCallSoFar, anthropicReasoning: null })
|
this._addMessageToThread(threadId, { role: 'assistant', displayContent: messageSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||||
this._setStreamState(threadId, { error }, 'set')
|
this._setStreamState(threadId, { error }, 'set')
|
||||||
resMessageIsDonePromise()
|
resMessageIsDonePromise()
|
||||||
},
|
},
|
||||||
|
|
@ -742,7 +711,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
// call tool if there is one
|
// call tool if there is one
|
||||||
const tool: RawToolCallObj | undefined = toolCall
|
const tool: RawToolCallObj | undefined = toolCall
|
||||||
if (tool) {
|
if (tool) {
|
||||||
const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, tool.name, { preapproved: false, unvalidatedToolParams: tool.rawParams })
|
const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, tool.name, tool.id, { preapproved: false, unvalidatedToolParams: tool.rawParams })
|
||||||
|
|
||||||
// stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools.
|
// stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools.
|
||||||
// just detect tool interruption which is the same as chat interruption right now
|
// just detect tool interruption which is the same as chat interruption right now
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,607 @@
|
||||||
|
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||||
|
import { deepClone } from '../../../../base/common/objects.js';
|
||||||
|
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||||
|
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||||
|
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||||
|
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||||
|
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||||
|
import { ChatMessage } from '../common/chatThreadServiceTypes.js';
|
||||||
|
import { getIsReasoningEnabledState, getMaxOutputTokens, getModelCapabilities } from '../common/modelCapabilities.js';
|
||||||
|
import { toolCallXMLStr, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
|
||||||
|
import { AnthropicLLMChatMessage, AnthropicReasoning, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||||
|
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||||
|
import { ChatMode, FeatureName, ModelSelection } from '../common/voidSettingsTypes.js';
|
||||||
|
import { IDirectoryStrService } from './directoryStrService.js';
|
||||||
|
import { ITerminalToolService } from './terminalToolService.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type SimpleLLMMessage = {
|
||||||
|
role: 'tool';
|
||||||
|
content: string;
|
||||||
|
id: string;
|
||||||
|
name: ToolName;
|
||||||
|
rawParams: RawToolParamsObj;
|
||||||
|
} | {
|
||||||
|
role: 'user';
|
||||||
|
content: string;
|
||||||
|
} | {
|
||||||
|
role: 'assistant';
|
||||||
|
content: string;
|
||||||
|
anthropicReasoning: AnthropicReasoning[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const EMPTY_MESSAGE = '(empty message)'
|
||||||
|
|
||||||
|
const CHARS_PER_TOKEN = 4
|
||||||
|
const TRIM_TO_LEN = 60
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// convert messages as if about to send to openai
|
||||||
|
/*
|
||||||
|
reference - https://platform.openai.com/docs/guides/function-calling#function-calling-steps
|
||||||
|
openai MESSAGE (role=assistant):
|
||||||
|
"tool_calls":[{
|
||||||
|
"type": "function",
|
||||||
|
"id": "call_12345xyz",
|
||||||
|
"function": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
|
||||||
|
}]
|
||||||
|
|
||||||
|
openai RESPONSE (role=user):
|
||||||
|
{ "role": "tool",
|
||||||
|
"tool_call_id": tool_call.id,
|
||||||
|
"content": str(result) }
|
||||||
|
|
||||||
|
also see
|
||||||
|
openai on prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
|
||||||
|
openai on developer system message - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): LLMChatMessage[] => {
|
||||||
|
|
||||||
|
const newMessages: OpenAILLMChatMessage[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < messages.length; i += 1) {
|
||||||
|
const currMsg = messages[i]
|
||||||
|
|
||||||
|
if (currMsg.role !== 'tool') {
|
||||||
|
newMessages.push(currMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// edit previous assistant message to have called the tool
|
||||||
|
const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined
|
||||||
|
if (prevMsg?.role === 'assistant') {
|
||||||
|
prevMsg.tool_calls = [{
|
||||||
|
type: 'function',
|
||||||
|
id: currMsg.id,
|
||||||
|
function: {
|
||||||
|
name: currMsg.name,
|
||||||
|
arguments: JSON.stringify(currMsg.rawParams)
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the tool
|
||||||
|
newMessages.push({
|
||||||
|
role: 'tool',
|
||||||
|
tool_call_id: currMsg.id,
|
||||||
|
content: currMsg.content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return newMessages
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// convert messages as if about to send to anthropic
|
||||||
|
/*
|
||||||
|
https://docs.anthropic.com/en/docs/build-with-claude/tool-use#tool-use-examples
|
||||||
|
anthropic MESSAGE (role=assistant):
|
||||||
|
"content": [{
|
||||||
|
"type": "text",
|
||||||
|
"text": "<thinking>I need to call the get_weather function, and the user wants SF, which is likely San Francisco, CA.</thinking>"
|
||||||
|
}, {
|
||||||
|
"type": "tool_use",
|
||||||
|
"id": "toolu_01A09q90qw90lq917835lq9",
|
||||||
|
"name": "get_weather",
|
||||||
|
"input": { "location": "San Francisco, CA", "unit": "celsius" }
|
||||||
|
}]
|
||||||
|
anthropic RESPONSE (role=user):
|
||||||
|
"content": [{
|
||||||
|
"type": "tool_result",
|
||||||
|
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
|
||||||
|
"content": "15 degrees"
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
Converts:
|
||||||
|
assistant: ...content
|
||||||
|
tool: (id, name, params)
|
||||||
|
->
|
||||||
|
assistant: ...content, call(name, id, params)
|
||||||
|
user: ...content, result(id, content)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): LLMChatMessage[] => {
|
||||||
|
const newMessages: (AnthropicLLMChatMessage | (SimpleLLMMessage & { role: 'tool' }))[] = messages;
|
||||||
|
|
||||||
|
for (let i = 0; i < messages.length; i += 1) {
|
||||||
|
const currMsg = messages[i]
|
||||||
|
|
||||||
|
// add anthropic reasoning
|
||||||
|
if (currMsg.role === 'assistant') {
|
||||||
|
if (currMsg.anthropicReasoning && supportsAnthropicReasoning) {
|
||||||
|
|
||||||
|
const content = currMsg.content
|
||||||
|
newMessages[i] = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: content ? [...currMsg.anthropicReasoning, { type: 'text' as const, text: content }] : currMsg.anthropicReasoning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currMsg.role === 'user') {
|
||||||
|
newMessages[i] = {
|
||||||
|
role: 'user',
|
||||||
|
content: currMsg.content,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currMsg.role === 'tool') {
|
||||||
|
// add anthropic tools
|
||||||
|
const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined
|
||||||
|
|
||||||
|
// make it so the assistant called the tool
|
||||||
|
if (prevMsg?.role === 'assistant') {
|
||||||
|
if (typeof prevMsg.content === 'string') prevMsg.content = [{ type: 'text', text: prevMsg.content }]
|
||||||
|
prevMsg.content.push({ type: 'tool_use', id: currMsg.id, name: currMsg.name, input: currMsg.rawParams })
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn each tool into a user message with tool results at the end
|
||||||
|
newMessages[i] = {
|
||||||
|
role: 'user',
|
||||||
|
content: [{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content }]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// we just removed the tools
|
||||||
|
return newMessages as AnthropicLLMChatMessage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): LLMChatMessage[] => {
|
||||||
|
|
||||||
|
const llmChatMessages: LLMChatMessage[] = [];
|
||||||
|
for (let i = 0; i < messages.length; i += 1) {
|
||||||
|
|
||||||
|
const c = messages[i]
|
||||||
|
const next = 0 <= i + 1 && i + 1 <= messages.length - 1 ? messages[i + 1] : null
|
||||||
|
|
||||||
|
if (c.role === 'assistant') {
|
||||||
|
// if called a tool (message after it), re-add its XML to the message
|
||||||
|
// alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere
|
||||||
|
let content: LLMChatMessage['content'] = c.content
|
||||||
|
if (next?.role === 'tool') {
|
||||||
|
content = `${content}\n\n${toolCallXMLStr(next.name, next.rawParams)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// anthropic reasoning
|
||||||
|
if (c.anthropicReasoning && supportsAnthropicReasoning) {
|
||||||
|
content = content ? [...c.anthropicReasoning, { type: 'text' as const, text: content }] : c.anthropicReasoning
|
||||||
|
}
|
||||||
|
llmChatMessages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// add user or tool to the previous user message
|
||||||
|
else if (c.role === 'user' || c.role === 'tool') {
|
||||||
|
if (c.role === 'tool')
|
||||||
|
c.content = `<${c.name}_result>\n${c.content}\n</${c.name}_result>`
|
||||||
|
|
||||||
|
if (llmChatMessages.length === 0 || llmChatMessages[llmChatMessages.length - 1].role !== 'user')
|
||||||
|
llmChatMessages.push({
|
||||||
|
role: 'user',
|
||||||
|
content: c.content
|
||||||
|
})
|
||||||
|
else
|
||||||
|
llmChatMessages[llmChatMessages.length - 1].content += '\n\n' + c.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return llmChatMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const prepareMessages_providerSpecific = (messages: SimpleLLMMessage[], specialToolFormat: 'openai-style' | 'anthropic-style' | undefined, supportsAnthropicReasoning: boolean): LLMChatMessage[] => {
|
||||||
|
const llmChatMessages: LLMChatMessage[] = []
|
||||||
|
if (!specialToolFormat) { // XML tool behavior
|
||||||
|
return prepareMessages_XML_tools(messages, supportsAnthropicReasoning)
|
||||||
|
}
|
||||||
|
else if (specialToolFormat === 'anthropic-style') {
|
||||||
|
return prepareMessages_anthropic_tools(messages, supportsAnthropicReasoning)
|
||||||
|
}
|
||||||
|
else if (specialToolFormat === 'openai-style') {
|
||||||
|
return prepareMessages_openai_tools(messages)
|
||||||
|
}
|
||||||
|
return llmChatMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- CHAT ---
|
||||||
|
|
||||||
|
const prepareMessages = ({
|
||||||
|
messages,
|
||||||
|
systemMessage,
|
||||||
|
aiInstructions,
|
||||||
|
supportsSystemMessage,
|
||||||
|
specialToolFormat,
|
||||||
|
supportsAnthropicReasoning,
|
||||||
|
contextWindow,
|
||||||
|
maxOutputTokens,
|
||||||
|
}: {
|
||||||
|
messages: SimpleLLMMessage[],
|
||||||
|
systemMessage: string,
|
||||||
|
aiInstructions: string,
|
||||||
|
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
|
||||||
|
specialToolFormat: 'openai-style' | 'anthropic-style' | undefined,
|
||||||
|
supportsAnthropicReasoning: boolean,
|
||||||
|
contextWindow: number,
|
||||||
|
maxOutputTokens: number | null | undefined,
|
||||||
|
}): { messages: LLMChatMessage[], separateSystemMessage: string | undefined } => {
|
||||||
|
maxOutputTokens = maxOutputTokens ?? 4_096 // default to 4096
|
||||||
|
|
||||||
|
// ================ trim ================
|
||||||
|
|
||||||
|
messages = deepClone(messages)
|
||||||
|
messages = messages.map(m => ({ ...m, content: m.role !== 'tool' ? m.content.trim() : m.content }))
|
||||||
|
|
||||||
|
// ================ fit into context ================
|
||||||
|
|
||||||
|
// the higher the weight, the higher the desire to truncate - TRIM HIGHEST WEIGHT MESSAGES
|
||||||
|
const alreadyTrimmedIdxes = new Set<number>()
|
||||||
|
const weight = (message: SimpleLLMMessage, messages: SimpleLLMMessage[], idx: number) => {
|
||||||
|
const base = message.content.length
|
||||||
|
|
||||||
|
let multiplier: number
|
||||||
|
multiplier = 1 + (messages.length - 1 - idx) / messages.length // slow rampdown from 2 to 1 as index increases
|
||||||
|
if (message.role === 'user') {
|
||||||
|
multiplier *= 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
multiplier *= 10 // llm tokens are far less valuable than user tokens
|
||||||
|
}
|
||||||
|
// 1st message, last 3 msgs, any already modified message should be low in weight
|
||||||
|
if (idx === 0 || idx >= messages.length - 1 - 3 || alreadyTrimmedIdxes.has(idx)) {
|
||||||
|
multiplier *= .05
|
||||||
|
}
|
||||||
|
return base * multiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
const _findLargestByWeight = (messages: SimpleLLMMessage[]) => {
|
||||||
|
let largestIndex = -1
|
||||||
|
let largestWeight = -Infinity
|
||||||
|
for (let i = 0; i < messages.length; i += 1) {
|
||||||
|
const m = messages[i]
|
||||||
|
const w = weight(m, messages, i)
|
||||||
|
if (w > largestWeight) {
|
||||||
|
largestWeight = w
|
||||||
|
largestIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return largestIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalLen = 0
|
||||||
|
for (const m of messages) { totalLen += m.content.length }
|
||||||
|
const charsNeedToTrim = totalLen - (contextWindow - maxOutputTokens) * CHARS_PER_TOKEN
|
||||||
|
|
||||||
|
// <----------------------------------------->
|
||||||
|
// 0 | | |
|
||||||
|
// | contextWindow |
|
||||||
|
// contextWindow - maxOut|putTokens
|
||||||
|
// totalLen
|
||||||
|
let remainingCharsToTrim = charsNeedToTrim
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (remainingCharsToTrim > 0) {
|
||||||
|
i += 1
|
||||||
|
if (i > 100) break
|
||||||
|
|
||||||
|
const trimIdx = _findLargestByWeight(messages)
|
||||||
|
const m = messages[trimIdx]
|
||||||
|
|
||||||
|
// if can finish here, do
|
||||||
|
const numCharsWillTrim = m.content.length - TRIM_TO_LEN
|
||||||
|
if (numCharsWillTrim > remainingCharsToTrim) {
|
||||||
|
m.content = m.content.slice(0, m.content.length - remainingCharsToTrim).trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingCharsToTrim -= numCharsWillTrim
|
||||||
|
m.content = m.content.substring(0, TRIM_TO_LEN - 3) + '...'
|
||||||
|
alreadyTrimmedIdxes.add(trimIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ tools and anthropicReasoning ================
|
||||||
|
const llmMessages: LLMChatMessage[] = prepareMessages_providerSpecific(messages, specialToolFormat, supportsAnthropicReasoning)
|
||||||
|
|
||||||
|
// ================ system message concat ================
|
||||||
|
|
||||||
|
// find system messages and concatenate them
|
||||||
|
const newSystemMessage = aiInstructions ?
|
||||||
|
`${(systemMessage ? `${systemMessage}\n\n` : '')}GUIDELINES\n${aiInstructions}`
|
||||||
|
: systemMessage
|
||||||
|
|
||||||
|
let separateSystemMessageStr: string | undefined = undefined
|
||||||
|
|
||||||
|
// if supports system message
|
||||||
|
if (supportsSystemMessage) {
|
||||||
|
if (supportsSystemMessage === 'separated')
|
||||||
|
separateSystemMessageStr = newSystemMessage
|
||||||
|
else if (supportsSystemMessage === 'system-role')
|
||||||
|
llmMessages.unshift({ role: 'system', content: newSystemMessage }) // add new first message
|
||||||
|
else if (supportsSystemMessage === 'developer-role')
|
||||||
|
llmMessages.unshift({ role: 'developer', content: newSystemMessage }) // add new first message
|
||||||
|
}
|
||||||
|
// if does not support system message
|
||||||
|
else {
|
||||||
|
const newFirstMessage = {
|
||||||
|
role: 'user',
|
||||||
|
content: `<SYSTEM_MESSAGE>\n${newSystemMessage}\n</SYSTEM_MESSAGE>\n${llmMessages[0].content}`
|
||||||
|
} as const
|
||||||
|
llmMessages.splice(0, 1) // delete first message
|
||||||
|
llmMessages.unshift(newFirstMessage) // add new first message
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ================ no empty message ================
|
||||||
|
for (const currMsg of llmMessages) {
|
||||||
|
if (currMsg.role === 'tool') continue
|
||||||
|
|
||||||
|
// if content is a string, replace string with empty msg
|
||||||
|
if (typeof currMsg.content === 'string')
|
||||||
|
currMsg.content = currMsg.content || EMPTY_MESSAGE
|
||||||
|
else {
|
||||||
|
// if content is an array, replace any empty text entries with empty msg, and make sure there's at least 1 entry
|
||||||
|
for (const c of currMsg.content) {
|
||||||
|
if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE
|
||||||
|
}
|
||||||
|
if (currMsg.content.length === 0) currMsg.content = [{ type: 'text', text: EMPTY_MESSAGE }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: llmMessages,
|
||||||
|
separateSystemMessage: separateSystemMessageStr,
|
||||||
|
} as const
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface IConvertToLLMMessageService {
|
||||||
|
readonly _serviceBrand: undefined;
|
||||||
|
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined };
|
||||||
|
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }>
|
||||||
|
prepareFIMMessage(opts: { messages: LLMFIMMessage, aiInstructions: string, }): { prefix: string, suffix: string, stopTokens: string[] }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IConvertToLLMMessageService = createDecorator<IConvertToLLMMessageService>('ConvertToLLMMessageService');
|
||||||
|
|
||||||
|
|
||||||
|
class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMessageService {
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IModelService private readonly modelService: IModelService,
|
||||||
|
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||||
|
@IEditorService private readonly editorService: IEditorService,
|
||||||
|
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
|
||||||
|
@ITerminalToolService private readonly terminalToolService: ITerminalToolService,
|
||||||
|
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// system message
|
||||||
|
private _generateChatMessagesSystemMessage = async (chatMode: ChatMode, specialToolFormat: 'openai-style' | 'anthropic-style' | undefined) => {
|
||||||
|
const workspaceFolders = this.workspaceContextService.getWorkspace().folders.map(f => f.uri.fsPath)
|
||||||
|
|
||||||
|
const openedURIs = this.modelService.getModels().filter(m => m.isAttachedToEditor()).map(m => m.uri.fsPath) || [];
|
||||||
|
const activeURI = this.editorService.activeEditor?.resource?.fsPath;
|
||||||
|
|
||||||
|
const directoryStr = await this.directoryStrService.getAllDirectoriesStr({
|
||||||
|
cutOffMessage: chatMode === 'agent' || chatMode === 'gather' ?
|
||||||
|
`...Directories string cut off, use tools to read more...`
|
||||||
|
: `...Directories string cut off, ask user for more if necessary...`
|
||||||
|
})
|
||||||
|
const includeXMLToolDefinitions = specialToolFormat === undefined
|
||||||
|
|
||||||
|
const runningTerminalIds = this.terminalToolService.listTerminalIds()
|
||||||
|
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode, includeXMLToolDefinitions })
|
||||||
|
return systemMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --- LLM Chat messages ---
|
||||||
|
|
||||||
|
private _chatMessagesToSimpleMessages(chatMessages: ChatMessage[]): SimpleLLMMessage[] {
|
||||||
|
const simpleLLMMessages: SimpleLLMMessage[] = []
|
||||||
|
|
||||||
|
for (const m of chatMessages) {
|
||||||
|
if (m.role === 'checkpoint') continue
|
||||||
|
if (m.role === 'interrupted_streaming_tool') continue
|
||||||
|
if (m.role === 'assistant') {
|
||||||
|
simpleLLMMessages.push({
|
||||||
|
role: m.role,
|
||||||
|
content: m.displayContent,
|
||||||
|
anthropicReasoning: m.anthropicReasoning,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (m.role === 'tool') {
|
||||||
|
simpleLLMMessages.push({
|
||||||
|
role: m.role,
|
||||||
|
content: m.content,
|
||||||
|
name: m.name,
|
||||||
|
id: m.id,
|
||||||
|
rawParams: m.rawParams,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (m.role === 'user') {
|
||||||
|
simpleLLMMessages.push({
|
||||||
|
role: m.role,
|
||||||
|
content: m.content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return simpleLLMMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareLLMSimpleMessages: IConvertToLLMMessageService['prepareLLMSimpleMessages'] = ({ simpleMessages, systemMessage, modelSelection, featureName }) => {
|
||||||
|
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
|
||||||
|
const { providerName, modelName } = modelSelection
|
||||||
|
const {
|
||||||
|
specialToolFormat,
|
||||||
|
contextWindow,
|
||||||
|
supportsSystemMessage,
|
||||||
|
} = getModelCapabilities(providerName, modelName)
|
||||||
|
|
||||||
|
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]
|
||||||
|
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
|
||||||
|
|
||||||
|
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
|
||||||
|
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
|
||||||
|
|
||||||
|
const { messages, separateSystemMessage } = prepareMessages({
|
||||||
|
messages: simpleMessages,
|
||||||
|
systemMessage,
|
||||||
|
aiInstructions,
|
||||||
|
supportsSystemMessage,
|
||||||
|
specialToolFormat,
|
||||||
|
supportsAnthropicReasoning: providerName === 'anthropic',
|
||||||
|
contextWindow,
|
||||||
|
maxOutputTokens,
|
||||||
|
})
|
||||||
|
return { messages, separateSystemMessage };
|
||||||
|
|
||||||
|
}
|
||||||
|
prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection }) => {
|
||||||
|
if (modelSelection === null) return { messages: [], separateSystemMessage: undefined }
|
||||||
|
const { providerName, modelName } = modelSelection
|
||||||
|
const {
|
||||||
|
specialToolFormat,
|
||||||
|
contextWindow,
|
||||||
|
supportsSystemMessage,
|
||||||
|
} = getModelCapabilities(providerName, modelName)
|
||||||
|
const systemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat)
|
||||||
|
|
||||||
|
const modelSelectionOptions = this.voidSettingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName]
|
||||||
|
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
|
||||||
|
|
||||||
|
const isReasoningEnabled = getIsReasoningEnabledState('Chat', providerName, modelName, modelSelectionOptions)
|
||||||
|
const maxOutputTokens = getMaxOutputTokens(providerName, modelName, { isReasoningEnabled })
|
||||||
|
const llmMessages = this._chatMessagesToSimpleMessages(chatMessages)
|
||||||
|
|
||||||
|
const { messages, separateSystemMessage } = prepareMessages({
|
||||||
|
messages: llmMessages,
|
||||||
|
systemMessage,
|
||||||
|
aiInstructions,
|
||||||
|
supportsSystemMessage,
|
||||||
|
specialToolFormat,
|
||||||
|
supportsAnthropicReasoning: providerName === 'anthropic',
|
||||||
|
contextWindow,
|
||||||
|
maxOutputTokens,
|
||||||
|
})
|
||||||
|
return { messages, separateSystemMessage };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- FIM ---
|
||||||
|
|
||||||
|
prepareFIMMessage: IConvertToLLMMessageService['prepareFIMMessage'] = ({ messages, aiInstructions }) => {
|
||||||
|
|
||||||
|
let prefix = `\
|
||||||
|
${!aiInstructions ? '' : `\
|
||||||
|
// Instructions:
|
||||||
|
// Do not output an explanation. Try to avoid outputting comments. Only output the middle code.
|
||||||
|
${aiInstructions.split('\n').map(line => `//${line}`).join('\n')}`}
|
||||||
|
|
||||||
|
${messages.prefix}`
|
||||||
|
|
||||||
|
const suffix = messages.suffix
|
||||||
|
const stopTokens = messages.stopTokens
|
||||||
|
return { prefix, suffix, stopTokens }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// pick one and delete the other:
|
||||||
|
registerSingleton(IConvertToLLMMessageService, ConvertToLLMMessageService, InstantiationType.Eager);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gemini has this, but they're openai-compat so we don't need to implement this
|
||||||
|
gemini request:
|
||||||
|
{ "role": "assistant",
|
||||||
|
"content": null,
|
||||||
|
"function_call": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"arguments": {
|
||||||
|
"latitude": 48.8566,
|
||||||
|
"longitude": 2.3522
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gemini response:
|
||||||
|
{ "role": "assistant",
|
||||||
|
"function_response": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"response": {
|
||||||
|
"temperature": "15°C",
|
||||||
|
"condition": "Cloudy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,6 +47,7 @@ import { IVoidModelService } from '../common/voidModelService.js';
|
||||||
import { deepClone } from '../../../../base/common/objects.js';
|
import { deepClone } from '../../../../base/common/objects.js';
|
||||||
import { acceptBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../common/helpers/colors.js';
|
import { acceptBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../common/helpers/colors.js';
|
||||||
import { DiffArea, Diff, CtrlKZone, VoidFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js';
|
import { DiffArea, Diff, CtrlKZone, VoidFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js';
|
||||||
|
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
||||||
|
|
||||||
const configOfBG = (color: Color) => {
|
const configOfBG = (color: Color) => {
|
||||||
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
||||||
|
|
@ -202,6 +203,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||||
// @IFileService private readonly _fileService: IFileService,
|
// @IFileService private readonly _fileService: IFileService,
|
||||||
@IVoidModelService private readonly _voidModelService: IVoidModelService,
|
@IVoidModelService private readonly _voidModelService: IVoidModelService,
|
||||||
|
@IConvertToLLMMessageService private readonly _convertToLLMMessageService: IConvertToLLMMessageService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
|
@ -1267,6 +1269,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
private _initializeWriteoverStream(opts: StartApplyingOpts): [DiffZone, Promise<void>] | undefined {
|
private _initializeWriteoverStream(opts: StartApplyingOpts): [DiffZone, Promise<void>] | undefined {
|
||||||
|
|
||||||
const { from, } = opts
|
const { from, } = opts
|
||||||
|
const featureName: FeatureName = opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K'
|
||||||
|
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||||
|
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||||
|
|
||||||
|
|
||||||
const uri = this._getURIBeforeStartApplying(opts)
|
const uri = this._getURIBeforeStartApplying(opts)
|
||||||
if (!uri) return
|
if (!uri) return
|
||||||
|
|
@ -1300,12 +1306,16 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
const originalCode = startRange === 'fullFile' ? originalFileCode : originalFileCode.split('\n').slice((startRange[0] - 1), (startRange[1] - 1) + 1).join('\n')
|
const originalCode = startRange === 'fullFile' ? originalFileCode : originalFileCode.split('\n').slice((startRange[0] - 1), (startRange[1] - 1) + 1).join('\n')
|
||||||
const language = model.getLanguageId()
|
const language = model.getLanguageId()
|
||||||
let messages: LLMChatMessage[]
|
let messages: LLMChatMessage[]
|
||||||
|
let separateSystemMessage: string | undefined
|
||||||
if (from === 'ClickApply') {
|
if (from === 'ClickApply') {
|
||||||
const userContent = rewriteCode_userMessage({ originalCode, applyStr: opts.applyStr, language })
|
const { messages: a, separateSystemMessage: b } = this._convertToLLMMessageService.prepareLLMSimpleMessages({
|
||||||
messages = [
|
systemMessage: rewriteCode_systemMessage,
|
||||||
{ role: 'system', content: rewriteCode_systemMessage, },
|
simpleMessages: [{ role: 'user', content: rewriteCode_userMessage({ originalCode, applyStr: opts.applyStr, language }), }],
|
||||||
{ role: 'user', content: userContent, }
|
featureName,
|
||||||
]
|
modelSelection,
|
||||||
|
})
|
||||||
|
messages = a
|
||||||
|
separateSystemMessage = b
|
||||||
}
|
}
|
||||||
else if (from === 'QuickEdit') {
|
else if (from === 'QuickEdit') {
|
||||||
if (!ctrlKZoneIfQuickEdit) return
|
if (!ctrlKZoneIfQuickEdit) return
|
||||||
|
|
@ -1316,11 +1326,16 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
const endLine = startRange === 'fullFile' ? model.getLineCount() : startRange[1]
|
const endLine = startRange === 'fullFile' ? model.getLineCount() : startRange[1]
|
||||||
const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: originalFileCode, startLine, endLine })
|
const { prefix, suffix } = voidPrefixAndSuffix({ fullFileStr: originalFileCode, startLine, endLine })
|
||||||
const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, fimTags: quickEditFIMTags, language })
|
const userContent = ctrlKStream_userMessage({ selection: originalCode, instructions: instructions, prefix, suffix, fimTags: quickEditFIMTags, language })
|
||||||
// type: 'messages',
|
|
||||||
messages = [
|
const { messages: a, separateSystemMessage: b } = this._convertToLLMMessageService.prepareLLMSimpleMessages({
|
||||||
{ role: 'system', content: ctrlKStream_systemMessage({ quickEditFIMTags: quickEditFIMTags }), },
|
systemMessage: ctrlKStream_systemMessage({ quickEditFIMTags: quickEditFIMTags }),
|
||||||
{ role: 'user', content: userContent, }
|
simpleMessages: [{ role: 'user', content: userContent, }],
|
||||||
]
|
featureName,
|
||||||
|
modelSelection,
|
||||||
|
})
|
||||||
|
messages = a
|
||||||
|
separateSystemMessage = b
|
||||||
|
|
||||||
}
|
}
|
||||||
else { throw new Error(`featureName ${from} is invalid`) }
|
else { throw new Error(`featureName ${from} is invalid`) }
|
||||||
|
|
||||||
|
|
@ -1384,10 +1399,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
|
|
||||||
const latestStreamLocationMutable: StreamLocationMutable = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
|
const latestStreamLocationMutable: StreamLocationMutable = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
|
||||||
|
|
||||||
const featureName: FeatureName = opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K'
|
|
||||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
|
||||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
|
||||||
|
|
||||||
// allowed to throw errors - this is called inside a promise that handles everything
|
// allowed to throw errors - this is called inside a promise that handles everything
|
||||||
const runWriteover = async () => {
|
const runWriteover = async () => {
|
||||||
let shouldSendAnotherMessage = true
|
let shouldSendAnotherMessage = true
|
||||||
|
|
@ -1410,6 +1421,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
messages,
|
messages,
|
||||||
modelSelection,
|
modelSelection,
|
||||||
modelSelectionOptions,
|
modelSelectionOptions,
|
||||||
|
separateSystemMessage,
|
||||||
chatMode: null, // not chat
|
chatMode: null, // not chat
|
||||||
onText: (params) => {
|
onText: (params) => {
|
||||||
const { fullText: fullText_ } = params
|
const { fullText: fullText_ } = params
|
||||||
|
|
@ -1485,6 +1497,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
|
|
||||||
private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise<void>] | undefined {
|
private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise<void>] | undefined {
|
||||||
const { from, applyStr, } = opts
|
const { from, applyStr, } = opts
|
||||||
|
const featureName: FeatureName = 'Apply'
|
||||||
|
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||||
|
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||||
|
|
||||||
const uri = this._getURIBeforeStartApplying(opts)
|
const uri = this._getURIBeforeStartApplying(opts)
|
||||||
if (!uri) return
|
if (!uri) return
|
||||||
|
|
@ -1498,10 +1513,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
// build messages - ask LLM to generate search/replace block text
|
// build messages - ask LLM to generate search/replace block text
|
||||||
const originalFileCode = model.getValue(EndOfLinePreference.LF)
|
const originalFileCode = model.getValue(EndOfLinePreference.LF)
|
||||||
const userMessageContent = searchReplace_userMessage({ originalCode: originalFileCode, applyStr: applyStr })
|
const userMessageContent = searchReplace_userMessage({ originalCode: originalFileCode, applyStr: applyStr })
|
||||||
const messages: LLMChatMessage[] = [
|
|
||||||
{ role: 'system', content: searchReplace_systemMessage },
|
const { messages, separateSystemMessage: separateSystemMessage } = this._convertToLLMMessageService.prepareLLMSimpleMessages({
|
||||||
{ role: 'user', content: userMessageContent },
|
systemMessage: searchReplace_systemMessage,
|
||||||
]
|
simpleMessages: [{ role: 'user', content: userMessageContent, }],
|
||||||
|
featureName,
|
||||||
|
modelSelection,
|
||||||
|
})
|
||||||
|
|
||||||
// if URI is already streaming, return (should never happen, caller is responsible for checking)
|
// if URI is already streaming, return (should never happen, caller is responsible for checking)
|
||||||
if (this._uriIsStreaming(uri)) return
|
if (this._uriIsStreaming(uri)) return
|
||||||
|
|
@ -1593,10 +1611,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
const addedTrackingZoneOfBlockNum: TrackingZone<SearchReplaceDiffAreaMetadata>[] = []
|
const addedTrackingZoneOfBlockNum: TrackingZone<SearchReplaceDiffAreaMetadata>[] = []
|
||||||
diffZone._streamState.line = 1
|
diffZone._streamState.line = 1
|
||||||
|
|
||||||
const featureName: FeatureName = 'Apply'
|
|
||||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
|
||||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
|
||||||
|
|
||||||
const N_RETRIES = 2
|
const N_RETRIES = 2
|
||||||
|
|
||||||
// allowed to throw errors - this is called inside a promise that handles everything
|
// allowed to throw errors - this is called inside a promise that handles everything
|
||||||
|
|
@ -1628,6 +1642,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
messages,
|
messages,
|
||||||
modelSelection,
|
modelSelection,
|
||||||
modelSelectionOptions,
|
modelSelectionOptions,
|
||||||
|
separateSystemMessage,
|
||||||
chatMode: null, // not chat
|
chatMode: null, // not chat
|
||||||
onText: (params) => {
|
onText: (params) => {
|
||||||
const { fullText } = params
|
const { fullText } = params
|
||||||
|
|
@ -1682,7 +1697,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
||||||
console.log('---------')
|
console.log('---------')
|
||||||
const content = errContentOfInvalidStr(originalBounds, block.orig, blockNum, blocks)
|
const content = errContentOfInvalidStr(originalBounds, block.orig, blockNum, blocks)
|
||||||
messages.push(
|
messages.push(
|
||||||
{ role: 'assistant', content: fullText, anthropicReasoning: null }, // latest output
|
{ role: 'assistant', content: fullText }, // latest output
|
||||||
{ role: 'user', content: content } // user explanation of what's wrong
|
{ role: 'user', content: content } // user explanation of what's wrong
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
||||||
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
||||||
|
|
||||||
let link = undefined
|
let link = undefined
|
||||||
if (rawText.endsWith("`")) { // if codespan was completed
|
if (rawText.endsWith('`')) { // if codespan was completed
|
||||||
|
|
||||||
// get link from cache
|
// get link from cache
|
||||||
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
||||||
|
|
@ -120,11 +120,11 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "space") {
|
if (t.type === 'space') {
|
||||||
return <span>{t.raw}</span>
|
return <span>{t.raw}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "code") {
|
if (t.type === 'code') {
|
||||||
const [firstLine, remainingContents] = separateOutFirstLine(t.text)
|
const [firstLine, remainingContents] = separateOutFirstLine(t.text)
|
||||||
const firstLineIsURI = isValidUri(firstLine) && !codeURI
|
const firstLineIsURI = isValidUri(firstLine) && !codeURI
|
||||||
const contents = firstLineIsURI ? (remainingContents?.trimStart() || '') : t.text // exclude first-line URI from contents
|
const contents = firstLineIsURI ? (remainingContents?.trimStart() || '') : t.text // exclude first-line URI from contents
|
||||||
|
|
@ -152,7 +152,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.isApplyEnabled && chatMessageLocation) {
|
if (options.isApplyEnabled && chatMessageLocation) {
|
||||||
const isCodeblockClosed = t.raw.trimEnd().endsWith('```') // user should only be able to Apply when the code has been closed (t.raw ends with "```")
|
const isCodeblockClosed = t.raw.trimEnd().endsWith('```') // user should only be able to Apply when the code has been closed (t.raw ends with '```')
|
||||||
|
|
||||||
const applyBoxId = getApplyBoxId({
|
const applyBoxId = getApplyBoxId({
|
||||||
threadId: chatMessageLocation.threadId,
|
threadId: chatMessageLocation.threadId,
|
||||||
|
|
@ -179,7 +179,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "heading") {
|
if (t.type === 'heading') {
|
||||||
|
|
||||||
const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements
|
const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements
|
||||||
|
|
||||||
|
|
@ -188,7 +188,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
</HeadingTag>
|
</HeadingTag>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "table") {
|
if (t.type === 'table') {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table>
|
||||||
|
|
@ -217,14 +217,14 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
)
|
)
|
||||||
// return (
|
// return (
|
||||||
// <div>
|
// <div>
|
||||||
// <table className={"min-w-full border border-void-bg-2"}>
|
// <table className={'min-w-full border border-void-bg-2'}>
|
||||||
// <thead>
|
// <thead>
|
||||||
// <tr className="bg-void-bg-1">
|
// <tr className='bg-void-bg-1'>
|
||||||
// {t.header.map((cell: any, index: number) => (
|
// {t.header.map((cell: any, index: number) => (
|
||||||
// <th
|
// <th
|
||||||
// key={index}
|
// key={index}
|
||||||
// className="px-4 py-2 border border-void-bg-2 font-semibold"
|
// className='px-4 py-2 border border-void-bg-2 font-semibold'
|
||||||
// style={{ textAlign: t.align[index] || "left" }}
|
// style={{ textAlign: t.align[index] || 'left' }}
|
||||||
// >
|
// >
|
||||||
// {cell.raw}
|
// {cell.raw}
|
||||||
// </th>
|
// </th>
|
||||||
|
|
@ -237,8 +237,8 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
// {row.map((cell: any, cellIndex: number) => (
|
// {row.map((cell: any, cellIndex: number) => (
|
||||||
// <td
|
// <td
|
||||||
// key={cellIndex}
|
// key={cellIndex}
|
||||||
// className={"px-4 py-2 border border-void-bg-2"}
|
// className={'px-4 py-2 border border-void-bg-2'}
|
||||||
// style={{ textAlign: t.align[cellIndex] || "left" }}
|
// style={{ textAlign: t.align[cellIndex] || 'left' }}
|
||||||
// >
|
// >
|
||||||
// {cell.raw}
|
// {cell.raw}
|
||||||
// </td>
|
// </td>
|
||||||
|
|
@ -251,32 +251,32 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
// )
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "hr") {
|
if (t.type === 'hr') {
|
||||||
return <hr />
|
return <hr />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "blockquote") {
|
if (t.type === 'blockquote') {
|
||||||
return <blockquote>{t.text}</blockquote>
|
return <blockquote>{t.text}</blockquote>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === 'list_item') {
|
if (t.type === 'list_item') {
|
||||||
return <li>
|
return <li>
|
||||||
<input type="checkbox" checked={t.checked} readOnly />
|
<input type='checkbox' checked={t.checked} readOnly />
|
||||||
<span>
|
<span>
|
||||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
|
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} inPTag={true} codeURI={codeURI} {...options} />
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "list") {
|
if (t.type === 'list') {
|
||||||
const ListTag = t.ordered ? "ol" : "ul"
|
const ListTag = t.ordered ? 'ol' : 'ul'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListTag start={t.start ? t.start : undefined}>
|
<ListTag start={t.start ? t.start : undefined}>
|
||||||
{t.items.map((item, index) => (
|
{t.items.map((item, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
{item.task && (
|
{item.task && (
|
||||||
<input type="checkbox" checked={item.checked} readOnly />
|
<input type='checkbox' checked={item.checked} readOnly />
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} inPTag={true} {...options} />
|
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} inPTag={true} {...options} />
|
||||||
|
|
@ -287,7 +287,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "paragraph") {
|
if (t.type === 'paragraph') {
|
||||||
const contents = <>
|
const contents = <>
|
||||||
{t.tokens.map((token, index) => (
|
{t.tokens.map((token, index) => (
|
||||||
<RenderToken key={index}
|
<RenderToken key={index}
|
||||||
|
|
@ -304,15 +304,15 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
return <p>{contents}</p>
|
return <p>{contents}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "text" || t.type === "escape") {
|
if (t.type === 'text' || t.type === 'escape' || t.type === 'html') {
|
||||||
return <span>{t.raw}</span>
|
return <span>{t.raw}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "def") {
|
if (t.type === 'def') {
|
||||||
return <></> // Definitions are typically not rendered
|
return <></> // Definitions are typically not rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "link") {
|
if (t.type === 'link') {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
onClick={() => { window.open(t.href) }}
|
onClick={() => { window.open(t.href) }}
|
||||||
|
|
@ -325,7 +325,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "image") {
|
if (t.type === 'image') {
|
||||||
return <img
|
return <img
|
||||||
src={t.href}
|
src={t.href}
|
||||||
alt={t.text}
|
alt={t.text}
|
||||||
|
|
@ -334,16 +334,16 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "strong") {
|
if (t.type === 'strong') {
|
||||||
return <strong>{t.text}</strong>
|
return <strong>{t.text}</strong>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "em") {
|
if (t.type === 'em') {
|
||||||
return <em>{t.text}</em>
|
return <em>{t.text}</em>
|
||||||
}
|
}
|
||||||
|
|
||||||
// inline code
|
// inline code
|
||||||
if (t.type === "codespan" || t.type === "html") {
|
if (t.type === 'codespan') {
|
||||||
|
|
||||||
if (options.isLinkDetectionEnabled && chatMessageLocation) {
|
if (options.isLinkDetectionEnabled && chatMessageLocation) {
|
||||||
return <CodespanWithLink
|
return <CodespanWithLink
|
||||||
|
|
@ -357,18 +357,18 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
||||||
return <Codespan text={t.text} />
|
return <Codespan text={t.text} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.type === "br") {
|
if (t.type === 'br') {
|
||||||
return <br />
|
return <br />
|
||||||
}
|
}
|
||||||
|
|
||||||
// strikethrough
|
// strikethrough
|
||||||
if (t.type === "del") {
|
if (t.type === 'del') {
|
||||||
return <del>{t.text}</del>
|
return <del>{t.text}</del>
|
||||||
}
|
}
|
||||||
// default
|
// default
|
||||||
return (
|
return (
|
||||||
<div className="bg-orange-50 rounded-sm overflow-hidden p-2">
|
<div className='bg-orange-50 rounded-sm overflow-hidden p-2'>
|
||||||
<span className="text-sm text-orange-500">Unknown token rendered...</span>
|
<span className='text-sm text-orange-500'>Unknown token rendered...</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg
|
||||||
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
||||||
import { error } from 'console';
|
import { error } from 'console';
|
||||||
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
|
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
|
||||||
|
import { MAX_FILE_CHARS_PAGE } from '../../../toolsService.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1298,7 +1299,7 @@ const ToolRequestAcceptRejectButtons = () => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
return <div className="flex gap-2 my-1 items-center">
|
return <div className="flex gap-2 mx-4 items-center">
|
||||||
{approveButton}
|
{approveButton}
|
||||||
{cancelButton}
|
{cancelButton}
|
||||||
{autoApproveToggle}
|
{autoApproveToggle}
|
||||||
|
|
@ -1434,17 +1435,17 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
||||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||||
|
|
||||||
if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) {
|
if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) {
|
||||||
const start = toolMessage.params.startLine === null ? `start` : `${toolMessage.params.startLine}`
|
const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}`
|
||||||
const end = toolMessage.params.endLine === null ? `end` : `${toolMessage.params.endLine}`
|
const end = toolMessage.params.endLine === null ? `` : `${toolMessage.params.endLine}`
|
||||||
const addStr = `(${start}-${end})`
|
const addStr = `(${start}-${end})`
|
||||||
componentParams.title += ` ${addStr}`
|
componentParams.desc1 += ` ${addStr}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolMessage.type === 'success') {
|
if (toolMessage.type === 'success') {
|
||||||
const { params, result } = toolMessage
|
const { params, result } = toolMessage
|
||||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||||
if (result.hasNextPage && params.pageNumber === 1) // first page
|
if (result.hasNextPage && params.pageNumber === 1) // first page
|
||||||
componentParams.desc2 = '(more content available)'
|
componentParams.desc2 = `(first ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)`
|
||||||
else if (params.pageNumber > 1) // subsequent pages
|
else if (params.pageNumber > 1) // subsequent pages
|
||||||
componentParams.desc2 = `(part ${params.pageNumber})`
|
componentParams.desc2 = `(part ${params.pageNumber})`
|
||||||
}
|
}
|
||||||
|
|
@ -2492,7 +2493,6 @@ export const SidebarChat = () => {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
displayContent: displayContentSoFar ?? '',
|
displayContent: displayContentSoFar ?? '',
|
||||||
reasoning: reasoningSoFar ?? '',
|
reasoning: reasoningSoFar ?? '',
|
||||||
toolCall: toolCallSoFar,
|
|
||||||
anthropicReasoning: null,
|
anthropicReasoning: null,
|
||||||
}}
|
}}
|
||||||
messageIdx={streamingChatIdx}
|
messageIdx={streamingChatIdx}
|
||||||
|
|
|
||||||
|
|
@ -312,12 +312,11 @@ export const ModelDump = () => {
|
||||||
</div>
|
</div>
|
||||||
{/* right part is anything that fits */}
|
{/* right part is anything that fits */}
|
||||||
<div className='flex items-center gap-4'
|
<div className='flex items-center gap-4'
|
||||||
data-tooltip-id='void-tooltip'
|
// data-tooltip-id='void-tooltip'
|
||||||
data-tooltip-place='top'
|
// data-tooltip-place='top'
|
||||||
data-tooltip-content={disabled? `${displayInfoOfProviderName(providerName).title} is disabled`
|
// data-tooltip-content={disabled ? `${displayInfoOfProviderName(providerName).title} is disabled`
|
||||||
: (isHidden ? `'${modelName}' won't appear in dropdowns` : ``)
|
// : (isHidden ? `'${modelName}' won't appear in dropdowns` : ``)
|
||||||
|
// }
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span className='opacity-50 truncate'>{isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'}</span>
|
<span className='opacity-50 truncate'>{isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'}</span>
|
||||||
|
|
||||||
|
|
@ -616,7 +615,19 @@ export const FeaturesTab = () => {
|
||||||
{/* FIM */}
|
{/* FIM */}
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>Experimental. Only works with models that support FIM.</div>
|
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
|
||||||
|
<span>
|
||||||
|
Experimental. Only works with FIM models.
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className='hover:brightness-110'
|
||||||
|
data-tooltip-id='void-tooltip'
|
||||||
|
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
|
||||||
|
data-tooltip-class-name='void-max-w-[20px]'
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='my-2'>
|
<div className='my-2'>
|
||||||
{/* Enable Switch */}
|
{/* Enable Switch */}
|
||||||
|
|
@ -696,7 +707,7 @@ export const FeaturesTab = () => {
|
||||||
value={voidSettingsState.globalSettings.includeToolLintErrors}
|
value={voidSettingsState.globalSettings.includeToolLintErrors}
|
||||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
|
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
|
||||||
/>
|
/>
|
||||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.includeToolLintErrors ? 'Include after-edit lint errors' : `Don't include lint errors`}</span>
|
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Don't fix lint errors`}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
/*--------------------------------------------------------------------------------------
|
|
||||||
* 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 interface ISearchReplaceService {
|
|
||||||
readonly _serviceBrand: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ISearchReplaceService = createDecorator<ISearchReplaceService>('SearchReplaceCacheService');
|
|
||||||
export class SearchReplaceService extends Disposable implements ISearchReplaceService {
|
|
||||||
_serviceBrand: undefined;
|
|
||||||
|
|
||||||
private readonly _onDidChangeState = new Emitter<void>();
|
|
||||||
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
// @ILLMMessageService private readonly llmMessageService: ILLMMessageService,
|
|
||||||
) {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
// send(params: ServiceSendLLMMessageParams & { onText: (p: { newText: string, fullText: string }) => { retry: boolean } }) {
|
|
||||||
// this.llmMessageService.sendLLMMessage({
|
|
||||||
// ...params as ServiceSendLLMMessageParams,
|
|
||||||
// onText: (p) => {
|
|
||||||
// const { retry } = params.onText(p)
|
|
||||||
// if (retry) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
registerSingleton(ISearchReplaceService, SearchReplaceService, InstantiationType.Eager);
|
|
||||||
|
|
@ -29,6 +29,32 @@ import { IChatThreadService } from './chatThreadService.js';
|
||||||
// ---------- Register commands and keybindings ----------
|
// ---------- Register commands and keybindings ----------
|
||||||
|
|
||||||
|
|
||||||
|
const findStagingSelectionIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): number | null => {
|
||||||
|
if (!currentSelections) return null
|
||||||
|
|
||||||
|
for (let i = 0; i < currentSelections.length; i += 1) {
|
||||||
|
const s = currentSelections[i]
|
||||||
|
|
||||||
|
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
||||||
|
|
||||||
|
if (s.type === 'File' && newSelection.type === 'File') {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') {
|
||||||
|
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
||||||
|
// if there's any collision return true
|
||||||
|
const [oldStart, oldEnd] = s.range
|
||||||
|
const [newStart, newEnd] = newSelection.range
|
||||||
|
if (oldStart !== newStart || oldEnd !== newEnd) continue
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if (s.type === 'Folder' && newSelection.type === 'Folder') {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export const roundRangeToLines = (range: IRange | null | undefined, options: { emptySelectionBehavior: 'null' | 'line' }) => {
|
export const roundRangeToLines = (range: IRange | null | undefined, options: { emptySelectionBehavior: 'null' | 'line' }) => {
|
||||||
if (!range)
|
if (!range)
|
||||||
return null
|
return null
|
||||||
|
|
@ -63,31 +89,6 @@ export const roundRangeToLines = (range: IRange | null | undefined, options: { e
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
const findStagingItemToReplace = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): [number, StagingSelectionItem] | null => {
|
|
||||||
if (!currentSelections) return null
|
|
||||||
|
|
||||||
for (let i = 0; i < currentSelections.length; i += 1) {
|
|
||||||
const s = currentSelections[i]
|
|
||||||
|
|
||||||
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
|
||||||
|
|
||||||
if (s.type === 'File' && newSelection.type === 'File') {
|
|
||||||
return [i, s] as const
|
|
||||||
}
|
|
||||||
if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') {
|
|
||||||
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
|
|
||||||
// if there's any collision return true
|
|
||||||
const [oldStart, oldEnd] = s.range
|
|
||||||
const [newStart, newEnd] = newSelection.range
|
|
||||||
if (oldStart !== newStart || oldEnd !== newEnd) continue
|
|
||||||
return [i, s] as const
|
|
||||||
}
|
|
||||||
if (s.type === 'Folder' && newSelection.type === 'Folder') {
|
|
||||||
return [i, s] as const
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const VOID_OPEN_SIDEBAR_ACTION_ID = 'void.sidebar.open'
|
const VOID_OPEN_SIDEBAR_ACTION_ID = 'void.sidebar.open'
|
||||||
registerAction2(class extends Action2 {
|
registerAction2(class extends Action2 {
|
||||||
|
|
@ -132,7 +133,7 @@ registerAction2(class extends Action2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const selection: StagingSelectionItem = !selectionRange || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
|
const newSelection: StagingSelectionItem = !selectionRange || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
|
||||||
type: 'File',
|
type: 'File',
|
||||||
uri: model.uri,
|
uri: model.uri,
|
||||||
language: model.getLanguageId(),
|
language: model.getLanguageId(),
|
||||||
|
|
@ -163,21 +164,17 @@ registerAction2(class extends Action2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if matches with existing selection, overwrite (since text may change)
|
// if matches with existing selection, overwrite (since text may change)
|
||||||
const replaceRes = findStagingItemToReplace(selections, selection)
|
const idx = findStagingSelectionIndex(selections, newSelection)
|
||||||
if (replaceRes) {
|
if (idx !== null && idx !== -1) {
|
||||||
const [idx, newSel] = replaceRes
|
setSelections([
|
||||||
|
...selections!.slice(0, idx),
|
||||||
if (idx !== undefined && idx !== -1) {
|
newSelection,
|
||||||
setSelections([
|
...selections!.slice(idx + 1, Infinity)
|
||||||
...selections!.slice(0, idx),
|
])
|
||||||
newSel,
|
|
||||||
...selections!.slice(idx + 1, Infinity)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if no match, add it
|
// if no match, add it
|
||||||
else {
|
else {
|
||||||
setSelections([...(selections ?? []), selection])
|
setSelections([...(selections ?? []), newSelection])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { EndOfLinePreference } from '../../../../editor/common/model.js'
|
||||||
import { basename } from '../../../../base/common/path.js'
|
import { basename } from '../../../../base/common/path.js'
|
||||||
import { IVoidCommandBarService } from './voidCommandBarService.js'
|
import { IVoidCommandBarService } from './voidCommandBarService.js'
|
||||||
import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from './directoryStrService.js'
|
import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from './directoryStrService.js'
|
||||||
import { IMarkerService } from '../../../../platform/markers/common/markers.js'
|
import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'
|
||||||
import { timeout } from '../../../../base/common/async.js'
|
import { timeout } from '../../../../base/common/async.js'
|
||||||
import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
|
import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
|
||||||
import { ToolName } from '../common/prompt/prompts.js'
|
import { ToolName } from '../common/prompt/prompts.js'
|
||||||
|
|
@ -276,8 +276,8 @@ export class ToolsService implements IToolsService {
|
||||||
const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1
|
const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1
|
||||||
const fileContents = contents.slice(fromIdx, toIdx + 1) // paginate
|
const fileContents = contents.slice(fromIdx, toIdx + 1) // paginate
|
||||||
const hasNextPage = (contents.length - 1) - toIdx >= 1
|
const hasNextPage = (contents.length - 1) - toIdx >= 1
|
||||||
|
const totalFileLen = contents.length
|
||||||
return { result: { fileContents, hasNextPage } }
|
return { result: { fileContents, totalFileLen, hasNextPage } }
|
||||||
},
|
},
|
||||||
|
|
||||||
ls_dir: async ({ rootURI, pageNumber }) => {
|
ls_dir: async ({ rootURI, pageNumber }) => {
|
||||||
|
|
@ -400,7 +400,7 @@ export class ToolsService implements IToolsService {
|
||||||
// given to the LLM after the call
|
// given to the LLM after the call
|
||||||
this.stringOfResult = {
|
this.stringOfResult = {
|
||||||
read_file: (params, result) => {
|
read_file: (params, result) => {
|
||||||
return result.fileContents + nextPageStr(result.hasNextPage)
|
return `${result.fileContents}${nextPageStr(result.hasNextPage)}${result.hasNextPage ? `This file has ${result.totalFileLen} characters, paginated ${MAX_FILE_CHARS_PAGE} at a time.` : ''}`
|
||||||
},
|
},
|
||||||
ls_dir: (params, result) => {
|
ls_dir: (params, result) => {
|
||||||
const dirTreeStr = stringifyDirectoryTree1Deep(params, result)
|
const dirTreeStr = stringifyDirectoryTree1Deep(params, result)
|
||||||
|
|
@ -471,9 +471,10 @@ export class ToolsService implements IToolsService {
|
||||||
private _getLintErrors(uri: URI): { lintErrors: LintErrorItem[] | null } {
|
private _getLintErrors(uri: URI): { lintErrors: LintErrorItem[] | null } {
|
||||||
const lintErrors = this.markerService
|
const lintErrors = this.markerService
|
||||||
.read({ resource: uri })
|
.read({ resource: uri })
|
||||||
|
.filter(l => l.severity === MarkerSeverity.Error || l.severity === MarkerSeverity.Warning)
|
||||||
.map(l => ({
|
.map(l => ({
|
||||||
code: typeof l.code === 'string' ? l.code : l.code?.value || '',
|
code: typeof l.code === 'string' ? l.code : l.code?.value || '',
|
||||||
message: l.message,
|
message: (l.severity === MarkerSeverity.Error ? '(error) ' : '(warning) ') + l.message,
|
||||||
startLineNumber: l.startLineNumber,
|
startLineNumber: l.startLineNumber,
|
||||||
endLineNumber: l.endLineNumber,
|
endLineNumber: l.endLineNumber,
|
||||||
} satisfies LintErrorItem))
|
} satisfies LintErrorItem))
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,17 @@
|
||||||
import { URI } from '../../../../base/common/uri.js';
|
import { URI } from '../../../../base/common/uri.js';
|
||||||
import { VoidFileSnapshot } from './editCodeServiceTypes.js';
|
import { VoidFileSnapshot } from './editCodeServiceTypes.js';
|
||||||
import { ToolName } from './prompt/prompts.js';
|
import { ToolName } from './prompt/prompts.js';
|
||||||
import { AnthropicReasoning, RawToolCallObj } from './sendLLMMessageTypes.js';
|
import { AnthropicReasoning, RawToolParamsObj } from './sendLLMMessageTypes.js';
|
||||||
import { ToolCallParams, ToolResultType } from './toolsServiceTypes.js';
|
import { ToolCallParams, ToolResultType } from './toolsServiceTypes.js';
|
||||||
|
|
||||||
export type ToolMessage<T extends ToolName> = {
|
export type ToolMessage<T extends ToolName> = {
|
||||||
role: 'tool';
|
role: 'tool';
|
||||||
content: string; // give this result to LLM (string of value)
|
content: string; // give this result to LLM (string of value)
|
||||||
|
id: string;
|
||||||
|
rawParams: RawToolParamsObj;
|
||||||
} & (
|
} & (
|
||||||
// in order of events:
|
// in order of events:
|
||||||
| { type: 'invalid_params', result: null, name: T, params: RawToolCallObj | null, }
|
| { type: 'invalid_params', result: null, name: T, }
|
||||||
|
|
||||||
| { type: 'tool_request', result: null, name: T, params: ToolCallParams[T], } // params were validated, awaiting user
|
| { type: 'tool_request', result: null, name: T, params: ToolCallParams[T], } // params were validated, awaiting user
|
||||||
|
|
||||||
|
|
@ -22,7 +24,7 @@ export type ToolMessage<T extends ToolName> = {
|
||||||
|
|
||||||
| { type: 'tool_error', result: string, name: T, params: ToolCallParams[T], } // error when tool was running
|
| { type: 'tool_error', result: string, name: T, params: ToolCallParams[T], } // error when tool was running
|
||||||
| { type: 'success', result: Awaited<ToolResultType[T]>, name: T, params: ToolCallParams[T], }
|
| { type: 'success', result: Awaited<ToolResultType[T]>, name: T, params: ToolCallParams[T], }
|
||||||
| { type: 'rejected', result: null, name: T, params: ToolCallParams[T], }
|
| { type: 'rejected', result: null, name: T, params: ToolCallParams[T] }
|
||||||
) // user rejected
|
) // user rejected
|
||||||
|
|
||||||
export type DecorativeCanceledTool = {
|
export type DecorativeCanceledTool = {
|
||||||
|
|
@ -58,7 +60,6 @@ export type ChatMessage =
|
||||||
role: 'assistant';
|
role: 'assistant';
|
||||||
displayContent: string; // content received from LLM - allowed to be '', will be replaced with (empty)
|
displayContent: string; // content received from LLM - allowed to be '', will be replaced with (empty)
|
||||||
reasoning: string; // reasoning from the LLM, used for step-by-step thinking
|
reasoning: string; // reasoning from the LLM, used for step-by-step thinking
|
||||||
toolCall: RawToolCallObj | undefined;
|
|
||||||
|
|
||||||
anthropicReasoning: AnthropicReasoning[] | null; // anthropic reasoning
|
anthropicReasoning: AnthropicReasoning[] | null; // anthropic reasoning
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ export type VoidStaticModelInfo = { // not stateful
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special paramete
|
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special paramete
|
||||||
|
specialToolFormat?: 'openai-style' | 'anthropic-style', // null defaults to XML
|
||||||
supportsFIM: boolean;
|
supportsFIM: boolean;
|
||||||
|
|
||||||
reasoningCapabilities: false | {
|
reasoningCapabilities: false | {
|
||||||
|
|
@ -377,6 +378,7 @@ const anthropicModelOptions = {
|
||||||
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'anthropic-style',
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
reasoningCapabilities: {
|
reasoningCapabilities: {
|
||||||
supportsReasoning: true,
|
supportsReasoning: true,
|
||||||
|
|
@ -385,6 +387,7 @@ const anthropicModelOptions = {
|
||||||
reasoningMaxOutputTokens: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19
|
reasoningMaxOutputTokens: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19
|
||||||
reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000
|
reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
'claude-3-5-sonnet-20241022': {
|
'claude-3-5-sonnet-20241022': {
|
||||||
contextWindow: 200_000,
|
contextWindow: 200_000,
|
||||||
|
|
@ -392,6 +395,7 @@ const anthropicModelOptions = {
|
||||||
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'anthropic-style',
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -401,6 +405,7 @@ const anthropicModelOptions = {
|
||||||
cost: { input: 0.80, cache_read: 0.08, cache_write: 1.00, output: 4.00 },
|
cost: { input: 0.80, cache_read: 0.08, cache_write: 1.00, output: 4.00 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'anthropic-style',
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -410,6 +415,7 @@ const anthropicModelOptions = {
|
||||||
cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 75.00 },
|
cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 75.00 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'anthropic-style',
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -418,6 +424,7 @@ const anthropicModelOptions = {
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
maxOutputTokens: 4_096,
|
maxOutputTokens: 4_096,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'anthropic-style',
|
||||||
supportsSystemMessage: 'separated',
|
supportsSystemMessage: 'separated',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
}
|
}
|
||||||
|
|
@ -457,6 +464,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||||
cost: { input: 2.00, output: 8.00, cache_read: 0.50 },
|
cost: { input: 2.00, output: 8.00, cache_read: 0.50 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'openai-style',
|
||||||
supportsSystemMessage: 'developer-role',
|
supportsSystemMessage: 'developer-role',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -466,6 +474,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||||
cost: { input: 0.40, output: 1.60, cache_read: 0.10 },
|
cost: { input: 0.40, output: 1.60, cache_read: 0.10 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'openai-style',
|
||||||
supportsSystemMessage: 'developer-role',
|
supportsSystemMessage: 'developer-role',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -475,6 +484,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||||
cost: { input: 0.10, output: 0.40, cache_read: 0.03 },
|
cost: { input: 0.10, output: 0.40, cache_read: 0.03 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'openai-style',
|
||||||
supportsSystemMessage: 'developer-role',
|
supportsSystemMessage: 'developer-role',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -502,6 +512,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||||
cost: { input: 2.50, cache_read: 1.25, output: 10.00, },
|
cost: { input: 2.50, cache_read: 1.25, output: 10.00, },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'openai-style',
|
||||||
supportsSystemMessage: 'system-role',
|
supportsSystemMessage: 'system-role',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -520,6 +531,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||||
cost: { input: 0.15, cache_read: 0.075, output: 0.60, },
|
cost: { input: 0.15, cache_read: 0.075, output: 0.60, },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
supportsFIM: false,
|
supportsFIM: false,
|
||||||
|
specialToolFormat: 'openai-style',
|
||||||
supportsSystemMessage: 'system-role', // ??
|
supportsSystemMessage: 'system-role', // ??
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
|
@ -550,6 +562,15 @@ const xAIModelOptions = {
|
||||||
supportsSystemMessage: 'system-role',
|
supportsSystemMessage: 'system-role',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
|
// 'grok-3': {
|
||||||
|
// contextWindow: 1_000_000,
|
||||||
|
// maxOutputTokens: null,
|
||||||
|
// cost: {},
|
||||||
|
// downloadable: false,
|
||||||
|
// supportsFIM: false,
|
||||||
|
// supportsSystemMessage: 'system-role',
|
||||||
|
// reasoningCapabilities: {canIOReasoning:false, canTurnOffReasoning:true,},
|
||||||
|
// }
|
||||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||||
|
|
||||||
const xAISettings: VoidStaticProviderInfo = {
|
const xAISettings: VoidStaticProviderInfo = {
|
||||||
|
|
@ -1032,6 +1053,14 @@ export const getIsReasoningEnabledState = (
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getMaxOutputTokens = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean }) => {
|
||||||
|
const {
|
||||||
|
reasoningCapabilities,
|
||||||
|
maxOutputTokens
|
||||||
|
} = getModelCapabilities(providerName, modelName)
|
||||||
|
return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
|
||||||
|
}
|
||||||
|
|
||||||
// used to force reasoning state (complex) into something simple we can just read from when sending a message
|
// used to force reasoning state (complex) into something simple we can just read from when sending a message
|
||||||
export const getSendableReasoningInfo = (
|
export const getSendableReasoningInfo = (
|
||||||
featureName: FeatureName,
|
featureName: FeatureName,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import { EndOfLinePreference } from '../../../../../editor/common/model.js';
|
import { EndOfLinePreference } from '../../../../../editor/common/model.js';
|
||||||
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
|
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
|
||||||
import { os } from '../helpers/systemInfo.js';
|
import { os } from '../helpers/systemInfo.js';
|
||||||
import { RawToolCallObj } from '../sendLLMMessageTypes.js';
|
import { RawToolParamsObj } from '../sendLLMMessageTypes.js';
|
||||||
import { toolNamesThatRequireApproval } from '../toolsServiceTypes.js';
|
import { toolNamesThatRequireApproval } from '../toolsServiceTypes.js';
|
||||||
import { IVoidModelService } from '../voidModelService.js';
|
import { IVoidModelService } from '../voidModelService.js';
|
||||||
import { ChatMode } from '../voidSettingsTypes.js';
|
import { ChatMode } from '../voidSettingsTypes.js';
|
||||||
|
|
@ -218,12 +218,11 @@ Format:
|
||||||
}).join('\n\n')}`
|
}).join('\n\n')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toolCallXMLStr = (toolCall: RawToolCallObj) => {
|
export const toolCallXMLStr = (toolName: ToolName, toolParams: RawToolParamsObj) => {
|
||||||
const t = toolCall
|
const params = Object.keys(toolParams).map(paramName => `<${paramName}>${toolParams[paramName as ToolParamName]}</${paramName}>`).join('\n')
|
||||||
const params = Object.keys(t.rawParams).map(paramName => `<${paramName}>${t.rawParams[paramName as ToolParamName]}</${paramName}>`).join('\n')
|
|
||||||
return `\
|
return `\
|
||||||
<${toolCall.name}>${!params ? '' : `\n${params}`}
|
<${toolName}>${!params ? '' : `\n${params}`}
|
||||||
</${toolCall.name}>`
|
</${toolName}>`
|
||||||
.replace('\t', ' ')
|
.replace('\t', ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +230,7 @@ export const toolCallXMLStr = (toolCall: RawToolCallObj) => {
|
||||||
// - You are allowed to call multiple tools by specifying them consecutively. However, there should be NO text or writing between tool calls or after them.
|
// - You are allowed to call multiple tools by specifying them consecutively. However, there should be NO text or writing between tool calls or after them.
|
||||||
const systemToolsXMLPrompt = (chatMode: ChatMode) => {
|
const systemToolsXMLPrompt = (chatMode: ChatMode) => {
|
||||||
const tools = availableTools(chatMode)
|
const tools = availableTools(chatMode)
|
||||||
if (!tools || tools.length === 0) return ''
|
if (!tools || tools.length === 0) return null
|
||||||
|
|
||||||
const toolXMLDefinitions = (`\
|
const toolXMLDefinitions = (`\
|
||||||
Available tools:
|
Available tools:
|
||||||
|
|
@ -255,7 +254,7 @@ ${toolCallXMLGuidelines}`
|
||||||
// ======================================================== chat (normal, gather, agent) ========================================================
|
// ======================================================== chat (normal, gather, agent) ========================================================
|
||||||
|
|
||||||
|
|
||||||
export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, runningTerminalIds, directoryStr, chatMode: mode }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, runningTerminalIds: string[], chatMode: ChatMode }) => {
|
export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, runningTerminalIds, directoryStr, chatMode: mode, includeXMLToolDefinitions }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, runningTerminalIds: string[], chatMode: ChatMode, includeXMLToolDefinitions: boolean }) => {
|
||||||
const header = (`You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} whose job is \
|
const header = (`You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} whose job is \
|
||||||
${mode === 'agent' ? `to help the user develop, run, and make changes to their codebase.`
|
${mode === 'agent' ? `to help the user develop, run, and make changes to their codebase.`
|
||||||
: mode === 'gather' ? `to search, understand, and reference files in the user's codebase.`
|
: mode === 'gather' ? `to search, understand, and reference files in the user's codebase.`
|
||||||
|
|
@ -289,7 +288,7 @@ ${directoryStr}
|
||||||
</files_overview>`)
|
</files_overview>`)
|
||||||
|
|
||||||
|
|
||||||
const toolDefinitions = systemToolsXMLPrompt(mode)
|
const toolDefinitions = includeXMLToolDefinitions ? systemToolsXMLPrompt(mode) : null
|
||||||
|
|
||||||
const details: string[] = []
|
const details: string[] = []
|
||||||
|
|
||||||
|
|
@ -630,39 +629,6 @@ ${tripleTick[1]}).`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const toAnthropicTool = (toolInfo: InternalToolInfo) => {
|
|
||||||
// const { name, description, params } = toolInfo
|
|
||||||
// return {
|
|
||||||
// name: name,
|
|
||||||
// description: description,
|
|
||||||
// input_schema: {
|
|
||||||
// type: 'object',
|
|
||||||
// properties: params,
|
|
||||||
// // required: Object.keys(params),
|
|
||||||
// },
|
|
||||||
// } satisfies Anthropic.Messages.Tool
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => {
|
|
||||||
// const { name, description, params } = toolInfo
|
|
||||||
// return {
|
|
||||||
// type: 'function',
|
|
||||||
// function: {
|
|
||||||
// name: name,
|
|
||||||
// // strict: true, // strict mode - https://platform.openai.com/docs/guides/function-calling?api-mode=chat
|
|
||||||
// description: description,
|
|
||||||
// parameters: {
|
|
||||||
// type: 'object',
|
|
||||||
// properties: params,
|
|
||||||
// // required: Object.keys(params), // in strict mode, all params are required and additionalProperties is false
|
|
||||||
// // additionalProperties: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// } satisfies OpenAI.Chat.Completions.ChatCompletionTool
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// ======================================================== ai search/replace ========================================================
|
// ======================================================== ai search/replace ========================================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { generateUuid } from '../../../../base/common/uuid.js';
|
||||||
import { Event } from '../../../../base/common/event.js';
|
import { Event } from '../../../../base/common/event.js';
|
||||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||||
// import { INotificationService } from '../../notification/common/notification.js';
|
|
||||||
|
|
||||||
// calls channel to implement features
|
// calls channel to implement features
|
||||||
export const ILLMMessageService = createDecorator<ILLMMessageService>('llmMessageService');
|
export const ILLMMessageService = createDecorator<ILLMMessageService>('llmMessageService');
|
||||||
|
|
@ -98,6 +97,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { settingsOfProvider, } = this.voidSettingsService.state
|
||||||
|
|
||||||
// add state for request id
|
// add state for request id
|
||||||
const requestId = generateUuid();
|
const requestId = generateUuid();
|
||||||
|
|
@ -106,13 +106,9 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
||||||
this.llmMessageHooks.onError[requestId] = onError
|
this.llmMessageHooks.onError[requestId] = onError
|
||||||
this.llmMessageHooks.onAbort[requestId] = onAbort // used internally only
|
this.llmMessageHooks.onAbort[requestId] = onAbort // used internally only
|
||||||
|
|
||||||
const { aiInstructions } = this.voidSettingsService.state.globalSettings
|
|
||||||
const { settingsOfProvider, } = this.voidSettingsService.state
|
|
||||||
|
|
||||||
// params will be stripped of all its functions over the IPC channel
|
// params will be stripped of all its functions over the IPC channel
|
||||||
this.channel.call('sendLLMMessage', {
|
this.channel.call('sendLLMMessage', {
|
||||||
...proxyParams,
|
...proxyParams,
|
||||||
aiInstructions,
|
|
||||||
requestId,
|
requestId,
|
||||||
settingsOfProvider,
|
settingsOfProvider,
|
||||||
modelSelection,
|
modelSelection,
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,39 @@ export const getErrorMessage: (error: unknown) => string = (error) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type LLMChatMessage = {
|
|
||||||
role: 'system';
|
export type AnthropicLLMChatMessage = {
|
||||||
content: string;
|
role: 'assistant',
|
||||||
|
content: string | (AnthropicReasoning | { type: 'text'; text: string }
|
||||||
|
| { type: 'tool_use'; name: string; input: Record<string, any>; id: string; }
|
||||||
|
)[];
|
||||||
} | {
|
} | {
|
||||||
role: 'user';
|
role: 'user',
|
||||||
|
content: string | (
|
||||||
|
{ type: 'text'; text: string; } | { type: 'tool_result'; tool_use_id: string; content: string; }
|
||||||
|
)[]
|
||||||
|
}
|
||||||
|
export type OpenAILLMChatMessage = {
|
||||||
|
role: 'system' | 'user' | 'developer';
|
||||||
content: string;
|
content: string;
|
||||||
} | {
|
} | {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: string; // text content
|
content: string | (AnthropicReasoning | { type: 'text'; text: string })[];
|
||||||
anthropicReasoning: AnthropicReasoning[] | null;
|
tool_calls?: { type: 'function'; id: string; function: { name: string; arguments: string; } }[];
|
||||||
|
} | {
|
||||||
|
role: 'tool',
|
||||||
|
content: string;
|
||||||
|
tool_call_id: string;
|
||||||
|
}
|
||||||
|
export type LLMChatMessage = AnthropicLLMChatMessage | OpenAILLMChatMessage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type LLMFIMMessage = {
|
||||||
|
prefix: string;
|
||||||
|
suffix: string;
|
||||||
|
stopTokens: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,10 +70,10 @@ export type RawToolCallObj = {
|
||||||
name: ToolName;
|
name: ToolName;
|
||||||
rawParams: RawToolParamsObj;
|
rawParams: RawToolParamsObj;
|
||||||
doneParams: ToolParamName[];
|
doneParams: ToolParamName[];
|
||||||
|
id: string;
|
||||||
isDone: boolean;
|
isDone: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type AnthropicReasoning = ({ type: 'thinking'; thinking: any; signature: string; } | { type: 'redacted_thinking', data: any })
|
export type AnthropicReasoning = ({ type: 'thinking'; thinking: any; signature: string; } | { type: 'redacted_thinking', data: any })
|
||||||
|
|
||||||
export type OnText = (p: { fullText: string; fullReasoning: string; toolCall?: RawToolCallObj }) => void
|
export type OnText = (p: { fullText: string; fullReasoning: string; toolCall?: RawToolCallObj }) => void
|
||||||
|
|
@ -60,23 +83,18 @@ export type OnAbort = () => void
|
||||||
export type AbortRef = { current: (() => void) | null }
|
export type AbortRef = { current: (() => void) | null }
|
||||||
|
|
||||||
|
|
||||||
export type LLMFIMMessage = {
|
// service types
|
||||||
prefix: string;
|
|
||||||
suffix: string;
|
|
||||||
stopTokens: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type SendLLMType = {
|
type SendLLMType = {
|
||||||
messagesType: 'chatMessages';
|
messagesType: 'chatMessages';
|
||||||
messages: LLMChatMessage[];
|
messages: LLMChatMessage[]; // the type of raw chat messages that we send to Anthropic, OAI, etc
|
||||||
|
separateSystemMessage: string | undefined;
|
||||||
chatMode: ChatMode | null;
|
chatMode: ChatMode | null;
|
||||||
} | {
|
} | {
|
||||||
messagesType: 'FIMMessage';
|
messagesType: 'FIMMessage';
|
||||||
messages: LLMFIMMessage;
|
messages: LLMFIMMessage;
|
||||||
|
separateSystemMessage?: undefined;
|
||||||
chatMode?: undefined;
|
chatMode?: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// service types
|
|
||||||
export type ServiceSendLLMMessageParams = {
|
export type ServiceSendLLMMessageParams = {
|
||||||
onText: OnText;
|
onText: OnText;
|
||||||
onFinalMessage: OnFinalMessage;
|
onFinalMessage: OnFinalMessage;
|
||||||
|
|
@ -95,8 +113,6 @@ export type SendLLMMessageParams = {
|
||||||
logging: { loggingName: string, loggingExtras?: { [k: string]: any } };
|
logging: { loggingName: string, loggingExtras?: { [k: string]: any } };
|
||||||
abortRef: AbortRef;
|
abortRef: AbortRef;
|
||||||
|
|
||||||
aiInstructions: string;
|
|
||||||
|
|
||||||
modelSelection: ModelSelection;
|
modelSelection: ModelSelection;
|
||||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export type ToolCallParams = {
|
||||||
|
|
||||||
// RESULT OF TOOL CALL
|
// RESULT OF TOOL CALL
|
||||||
export type ToolResultType = {
|
export type ToolResultType = {
|
||||||
'read_file': { fileContents: string, hasNextPage: boolean },
|
'read_file': { fileContents: string, totalFileLen: number, hasNextPage: boolean },
|
||||||
'ls_dir': { children: ShallowDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
|
'ls_dir': { children: ShallowDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
|
||||||
'get_dir_structure': { str: string, },
|
'get_dir_structure': { str: string, },
|
||||||
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||||
*--------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { generateUuid } from '../../../../../base/common/uuid.js'
|
||||||
import { endsWithAnyPrefixOf, SurroundingsRemover } from '../../common/helpers/extractCodeFromResult.js'
|
import { endsWithAnyPrefixOf, SurroundingsRemover } from '../../common/helpers/extractCodeFromResult.js'
|
||||||
import { availableTools, InternalToolInfo, ToolName, ToolParamName } from '../../common/prompt/prompts.js'
|
import { availableTools, InternalToolInfo, ToolName, ToolParamName } from '../../common/prompt/prompts.js'
|
||||||
import { OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'
|
import { OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'
|
||||||
|
|
@ -134,7 +135,7 @@ export const extractReasoningWrapper = (
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// =============== tools ===============
|
// =============== tools (XML) ===============
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -160,7 +161,7 @@ const findIndexOfAny = (fullText: string, matches: string[]) => {
|
||||||
|
|
||||||
|
|
||||||
type ToolOfToolName = { [toolName: string]: InternalToolInfo | undefined }
|
type ToolOfToolName = { [toolName: string]: InternalToolInfo | undefined }
|
||||||
const parseXMLPrefixToToolCall = (toolName: ToolName, str: string, toolOfToolName: ToolOfToolName): RawToolCallObj => {
|
const parseXMLPrefixToToolCall = (toolName: ToolName, toolId: string, str: string, toolOfToolName: ToolOfToolName): RawToolCallObj => {
|
||||||
const paramsObj: RawToolParamsObj = {}
|
const paramsObj: RawToolParamsObj = {}
|
||||||
const doneParams: ToolParamName[] = []
|
const doneParams: ToolParamName[] = []
|
||||||
let isDone = false
|
let isDone = false
|
||||||
|
|
@ -179,7 +180,8 @@ const parseXMLPrefixToToolCall = (toolName: ToolName, str: string, toolOfToolNam
|
||||||
name: toolName,
|
name: toolName,
|
||||||
rawParams: paramsObj,
|
rawParams: paramsObj,
|
||||||
doneParams: doneParams,
|
doneParams: doneParams,
|
||||||
isDone: isDone
|
isDone: isDone,
|
||||||
|
id: toolId,
|
||||||
}
|
}
|
||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
@ -254,9 +256,11 @@ const parseXMLPrefixToToolCall = (toolName: ToolName, str: string, toolOfToolNam
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractToolsWrapper = (
|
export const extractXMLToolsWrapper = (
|
||||||
onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode
|
onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode | null
|
||||||
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
||||||
|
|
||||||
|
if (!chatMode) return { newOnText: onText, newOnFinalMessage: onFinalMessage }
|
||||||
const tools = availableTools(chatMode)
|
const tools = availableTools(chatMode)
|
||||||
if (!tools) return { newOnText: onText, newOnFinalMessage: onFinalMessage }
|
if (!tools) return { newOnText: onText, newOnFinalMessage: onFinalMessage }
|
||||||
|
|
||||||
|
|
@ -264,6 +268,8 @@ export const extractToolsWrapper = (
|
||||||
const toolOpenTags = tools.map(t => `<${t.name}>`)
|
const toolOpenTags = tools.map(t => `<${t.name}>`)
|
||||||
for (const t of tools) { toolOfToolName[t.name] = t }
|
for (const t of tools) { toolOfToolName[t.name] = t }
|
||||||
|
|
||||||
|
const toolId = generateUuid()
|
||||||
|
|
||||||
// detect <availableTools[0]></availableTools[0]>, etc
|
// detect <availableTools[0]></availableTools[0]>, etc
|
||||||
let fullText = '';
|
let fullText = '';
|
||||||
let trueFullText = ''
|
let trueFullText = ''
|
||||||
|
|
@ -315,14 +321,12 @@ export const extractToolsWrapper = (
|
||||||
if (foundOpenTag !== null) {
|
if (foundOpenTag !== null) {
|
||||||
latestToolCall = parseXMLPrefixToToolCall(
|
latestToolCall = parseXMLPrefixToToolCall(
|
||||||
foundOpenTag.toolName,
|
foundOpenTag.toolName,
|
||||||
|
toolId,
|
||||||
trueFullText.substring(foundOpenTag.idx, Infinity),
|
trueFullText.substring(foundOpenTag.idx, Infinity),
|
||||||
toolOfToolName,
|
toolOfToolName,
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onText({
|
onText({
|
||||||
...params,
|
...params,
|
||||||
fullText,
|
fullText,
|
||||||
|
|
|
||||||
|
|
@ -1,524 +0,0 @@
|
||||||
/*--------------------------------------------------------------------------------------
|
|
||||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
|
||||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
|
||||||
*--------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { AnthropicReasoning, LLMChatMessage, LLMFIMMessage } from '../../common/sendLLMMessageTypes.js';
|
|
||||||
import { deepClone } from '../../../../../base/common/objects.js';
|
|
||||||
|
|
||||||
|
|
||||||
export const parseObject = (args: unknown) => {
|
|
||||||
if (typeof args === 'object')
|
|
||||||
return args
|
|
||||||
if (typeof args === 'string')
|
|
||||||
try { return JSON.parse(args) }
|
|
||||||
catch (e) { return { args } }
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type InternalLLMChatMessage = {
|
|
||||||
role: 'system' | 'user';
|
|
||||||
content: string;
|
|
||||||
} | {
|
|
||||||
role: 'assistant',
|
|
||||||
content: string | (AnthropicReasoning | { type: 'text'; text: string })[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const EMPTY_MESSAGE = '(empty message)'
|
|
||||||
|
|
||||||
const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatMessage[] }): { messages: LLMChatMessage[] } => {
|
|
||||||
const messages = deepClone(messages_)
|
|
||||||
const newMessages: LLMChatMessage[] = []
|
|
||||||
if (messages.length >= 0) newMessages.push(messages[0])
|
|
||||||
|
|
||||||
// remove duplicate roles - we used to do this, but we don't anymore
|
|
||||||
for (let i = 1; i < messages.length; i += 1) {
|
|
||||||
const m = messages[i]
|
|
||||||
newMessages.push(m)
|
|
||||||
}
|
|
||||||
const finalMessages = newMessages.map(m => ({ ...m, content: m.content.trim() }))
|
|
||||||
return { messages: finalMessages }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const CHARS_PER_TOKEN = 4
|
|
||||||
const TRIM_TO_LEN = 60
|
|
||||||
|
|
||||||
const prepareMessages_fitIntoContext = ({ messages, contextWindow, maxOutputTokens }: { messages: LLMChatMessage[], contextWindow: number, maxOutputTokens: number }): { messages: LLMChatMessage[] } => {
|
|
||||||
|
|
||||||
// the higher the weight, the higher the desire to truncate
|
|
||||||
const alreadyTrimmedIdxes = new Set<number>()
|
|
||||||
const weight = (message: LLMChatMessage, messages: LLMChatMessage[], idx: number) => {
|
|
||||||
const base = message.content.length
|
|
||||||
|
|
||||||
let multiplier: number
|
|
||||||
if (message.role === 'system')
|
|
||||||
return 0 // never erase system message
|
|
||||||
|
|
||||||
multiplier = 1 + (messages.length - 1 - idx) / messages.length // slow rampdown from 2 to 1 as index increases
|
|
||||||
if (message.role === 'user') {
|
|
||||||
multiplier *= 1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
multiplier *= 10 // llm tokens are far less valuable than user tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1st message, last 3 msgs, any already modified message should be low in weight
|
|
||||||
if (idx === 0 || idx >= messages.length - 1 - 3 || alreadyTrimmedIdxes.has(idx)) {
|
|
||||||
multiplier *= .05
|
|
||||||
}
|
|
||||||
|
|
||||||
return base * multiplier
|
|
||||||
|
|
||||||
}
|
|
||||||
const _findLargestByWeight = (messages: LLMChatMessage[]) => {
|
|
||||||
let largestIndex = -1
|
|
||||||
let largestWeight = -Infinity
|
|
||||||
for (let i = 0; i < messages.length; i += 1) {
|
|
||||||
const m = messages[i]
|
|
||||||
const w = weight(m, messages, i)
|
|
||||||
if (w > largestWeight) {
|
|
||||||
largestWeight = w
|
|
||||||
largestIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return largestIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalLen = 0
|
|
||||||
for (const m of messages) { totalLen += m.content.length }
|
|
||||||
const charsNeedToTrim = totalLen - (contextWindow - maxOutputTokens) * CHARS_PER_TOKEN
|
|
||||||
if (charsNeedToTrim <= 0) return { messages }
|
|
||||||
|
|
||||||
// <----------------------------------------->
|
|
||||||
// 0 | | |
|
|
||||||
// | contextWindow |
|
|
||||||
// contextWindow - maxOut|putTokens
|
|
||||||
// |
|
|
||||||
// totalLen
|
|
||||||
|
|
||||||
|
|
||||||
// TRIM HIGHEST WEIGHT MESSAGES
|
|
||||||
let remainingCharsToTrim = charsNeedToTrim
|
|
||||||
let i = 0
|
|
||||||
|
|
||||||
while (remainingCharsToTrim > 0) {
|
|
||||||
i += 1
|
|
||||||
if (i > 100) break
|
|
||||||
|
|
||||||
const trimIdx = _findLargestByWeight(messages)
|
|
||||||
const m = messages[trimIdx]
|
|
||||||
|
|
||||||
// if can finish here, do
|
|
||||||
const numCharsWillTrim = m.content.length - TRIM_TO_LEN
|
|
||||||
if (numCharsWillTrim > remainingCharsToTrim) {
|
|
||||||
m.content = m.content.slice(0, m.content.length - remainingCharsToTrim)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingCharsToTrim -= numCharsWillTrim
|
|
||||||
m.content = m.content.substring(0, TRIM_TO_LEN - 3) + '...'
|
|
||||||
alreadyTrimmedIdxes.add(trimIdx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { messages }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// no matter whether the model supports a system message or not (or what format it supports), add it in some way
|
|
||||||
const prepareMessages_addSystemInstructions = ({
|
|
||||||
messages,
|
|
||||||
aiInstructions,
|
|
||||||
supportsSystemMessage,
|
|
||||||
}: {
|
|
||||||
messages: InternalLLMChatMessage[],
|
|
||||||
aiInstructions: string,
|
|
||||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
|
|
||||||
})
|
|
||||||
: { separateSystemMessageStr?: string, messages: any[] } => {
|
|
||||||
|
|
||||||
// find system messages and concatenate them
|
|
||||||
let systemMessageStr = messages
|
|
||||||
.filter(msg => msg.role === 'system')
|
|
||||||
.map(msg => msg.content)
|
|
||||||
.join('\n') || undefined;
|
|
||||||
|
|
||||||
if (aiInstructions)
|
|
||||||
systemMessageStr = `${(systemMessageStr ? `${systemMessageStr}\n\n` : '')}GUIDELINES\n${aiInstructions}`
|
|
||||||
|
|
||||||
let separateSystemMessageStr: string | undefined = undefined
|
|
||||||
|
|
||||||
// remove all system messages
|
|
||||||
const newMessages: (InternalLLMChatMessage | { role: 'developer', content: string })[] = messages.filter(msg => msg.role !== 'system')
|
|
||||||
|
|
||||||
|
|
||||||
// if it has a system message (if doesn't, we obviously don't care about whether it supports system message or not...)
|
|
||||||
if (systemMessageStr) {
|
|
||||||
// if supports system message
|
|
||||||
if (supportsSystemMessage) {
|
|
||||||
if (supportsSystemMessage === 'separated')
|
|
||||||
separateSystemMessageStr = systemMessageStr
|
|
||||||
else if (supportsSystemMessage === 'system-role')
|
|
||||||
newMessages.unshift({ role: 'system', content: systemMessageStr }) // add new first message
|
|
||||||
else if (supportsSystemMessage === 'developer-role')
|
|
||||||
newMessages.unshift({ role: 'developer', content: systemMessageStr }) // add new first message
|
|
||||||
}
|
|
||||||
// if does not support system message
|
|
||||||
else {
|
|
||||||
const newFirstMessage = {
|
|
||||||
role: 'user',
|
|
||||||
content: (''
|
|
||||||
+ '<SYSTEM_MESSAGE>\n'
|
|
||||||
+ systemMessageStr
|
|
||||||
+ '\n'
|
|
||||||
+ '</SYSTEM_MESSAGE>\n'
|
|
||||||
+ newMessages[0].content
|
|
||||||
)
|
|
||||||
} as const
|
|
||||||
newMessages.splice(0, 1) // delete first message
|
|
||||||
newMessages.unshift(newFirstMessage) // add new first message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { messages: newMessages, separateSystemMessageStr }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // convert messages as if about to send to openai
|
|
||||||
// /*
|
|
||||||
// reference - https://platform.openai.com/docs/guides/function-calling#function-calling-steps
|
|
||||||
// openai MESSAGE (role=assistant):
|
|
||||||
// "tool_calls":[{
|
|
||||||
// "type": "function",
|
|
||||||
// "id": "call_12345xyz",
|
|
||||||
// "function": {
|
|
||||||
// "name": "get_weather",
|
|
||||||
// "arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
|
|
||||||
// }]
|
|
||||||
|
|
||||||
// openai RESPONSE (role=user):
|
|
||||||
// { "role": "tool",
|
|
||||||
// "tool_call_id": tool_call.id,
|
|
||||||
// "content": str(result) }
|
|
||||||
|
|
||||||
// also see
|
|
||||||
// openai on prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
|
|
||||||
// openai on developer system message - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
|
|
||||||
// */
|
|
||||||
|
|
||||||
// type PrepareMessagesToolsOpenAI = (
|
|
||||||
// Exclude<InternalLLMChatMessage, { role: 'assistant' | 'tool' }> | {
|
|
||||||
// role: 'assistant',
|
|
||||||
// content: string | (AnthropicReasoning | { type: 'text'; text: string })[];
|
|
||||||
// tool_calls?: {
|
|
||||||
// type: 'function';
|
|
||||||
// id: string;
|
|
||||||
// function: {
|
|
||||||
// name: string;
|
|
||||||
// arguments: string;
|
|
||||||
// }
|
|
||||||
// }[]
|
|
||||||
// } | {
|
|
||||||
// role: 'tool',
|
|
||||||
// tool_call_id: string;
|
|
||||||
// content: string;
|
|
||||||
// }
|
|
||||||
// )[]
|
|
||||||
// const prepareMessages_tools_openai = ({ messages }: { messages: InternalLLMChatMessage[], }) => {
|
|
||||||
|
|
||||||
// const newMessages: PrepareMessagesToolsOpenAI = [];
|
|
||||||
|
|
||||||
// for (let i = 0; i < messages.length; i += 1) {
|
|
||||||
// const currMsg = messages[i]
|
|
||||||
|
|
||||||
// if (currMsg.role !== 'tool') {
|
|
||||||
// newMessages.push(currMsg)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // edit previous assistant message to have called the tool
|
|
||||||
// const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined
|
|
||||||
// if (prevMsg?.role === 'assistant') {
|
|
||||||
// prevMsg.tool_calls = [{
|
|
||||||
// type: 'function',
|
|
||||||
// id: currMsg.id,
|
|
||||||
// function: {
|
|
||||||
// name: currMsg.name,
|
|
||||||
// arguments: JSON.stringify(currMsg.params)
|
|
||||||
// }
|
|
||||||
// }]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // add the tool
|
|
||||||
// newMessages.push({
|
|
||||||
// role: 'tool',
|
|
||||||
// tool_call_id: currMsg.id,
|
|
||||||
// content: currMsg.content || EMPTY_TOOL_CONTENT,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// return { messages: newMessages }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// // convert messages as if about to send to anthropic
|
|
||||||
// /*
|
|
||||||
// https://docs.anthropic.com/en/docs/build-with-claude/tool-use#tool-use-examples
|
|
||||||
// anthropic MESSAGE (role=assistant):
|
|
||||||
// "content": [{
|
|
||||||
// "type": "text",
|
|
||||||
// "text": "<thinking>I need to call the get_weather function, and the user wants SF, which is likely San Francisco, CA.</thinking>"
|
|
||||||
// }, {
|
|
||||||
// "type": "tool_use",
|
|
||||||
// "id": "toolu_01A09q90qw90lq917835lq9",
|
|
||||||
// "name": "get_weather",
|
|
||||||
// "input": { "location": "San Francisco, CA", "unit": "celsius" }
|
|
||||||
// }]
|
|
||||||
// anthropic RESPONSE (role=user):
|
|
||||||
// "content": [{
|
|
||||||
// "type": "tool_result",
|
|
||||||
// "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
|
|
||||||
// "content": "15 degrees"
|
|
||||||
// }]
|
|
||||||
// */
|
|
||||||
|
|
||||||
// type PrepareMessagesToolsAnthropic = (
|
|
||||||
// Exclude<InternalLLMChatMessage, { role: 'assistant' | 'user' }> | {
|
|
||||||
// role: 'assistant',
|
|
||||||
// content: string | (
|
|
||||||
// | AnthropicReasoning
|
|
||||||
// | {
|
|
||||||
// type: 'text';
|
|
||||||
// text: string;
|
|
||||||
// }
|
|
||||||
// | {
|
|
||||||
// type: 'tool_use';
|
|
||||||
// name: string;
|
|
||||||
// input: Record<string, any>;
|
|
||||||
// id: string;
|
|
||||||
// })[]
|
|
||||||
// } | {
|
|
||||||
// role: 'user',
|
|
||||||
// content: string | ({
|
|
||||||
// type: 'text';
|
|
||||||
// text: string;
|
|
||||||
// } | {
|
|
||||||
// type: 'tool_result';
|
|
||||||
// tool_use_id: string;
|
|
||||||
// content: string;
|
|
||||||
// })[]
|
|
||||||
// }
|
|
||||||
// )[]
|
|
||||||
// /*
|
|
||||||
// Converts:
|
|
||||||
|
|
||||||
// assistant: ...content
|
|
||||||
// tool: (id, name, params)
|
|
||||||
// ->
|
|
||||||
// assistant: ...content, call(name, id, params)
|
|
||||||
// user: ...content, result(id, content)
|
|
||||||
// */
|
|
||||||
// const prepareMessages_tools_anthropic = ({ messages }: { messages: InternalLLMChatMessage[], }) => {
|
|
||||||
// const newMessages: PrepareMessagesToolsAnthropic = messages;
|
|
||||||
|
|
||||||
|
|
||||||
// for (let i = 0; i < newMessages.length; i += 1) {
|
|
||||||
// const currMsg = newMessages[i]
|
|
||||||
|
|
||||||
// if (currMsg.role !== 'tool') continue
|
|
||||||
|
|
||||||
// const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined
|
|
||||||
|
|
||||||
// if (prevMsg?.role === 'assistant') {
|
|
||||||
// if (typeof prevMsg.content === 'string') prevMsg.content = [{ type: 'text', text: prevMsg.content }]
|
|
||||||
// prevMsg.content.push({ type: 'tool_use', id: currMsg.id, name: currMsg.name, input: parseObject(currMsg.params) })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // turn each tool into a user message with tool results at the end
|
|
||||||
// newMessages[i] = {
|
|
||||||
// role: 'user',
|
|
||||||
// content: [
|
|
||||||
// ...[{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content || EMPTY_TOOL_CONTENT }] as const,
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return { messages: newMessages }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// type PrepareMessagesTools = PrepareMessagesToolsAnthropic | PrepareMessagesToolsOpenAI
|
|
||||||
|
|
||||||
// const prepareMessages_tools = ({ messages, supportsTools }: { messages: InternalLLMChatMessage[], supportsTools: false | 'TODO-yes-but-we-handle-it-manually' | 'anthropic-style' | 'openai-style' }): { messages: PrepareMessagesTools } => {
|
|
||||||
// if (!supportsTools) {
|
|
||||||
// return { messages: messages }
|
|
||||||
// }
|
|
||||||
// else if (supportsTools === 'anthropic-style') {
|
|
||||||
// return prepareMessages_tools_anthropic({ messages })
|
|
||||||
// }
|
|
||||||
// else if (supportsTools === 'openai-style') {
|
|
||||||
// return prepareMessages_tools_openai({ messages })
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// throw new Error(`supportsTools type not recognized`)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// remove rawAnthropicAssistantContent, and make content equal to it if supportsAnthropicContent
|
|
||||||
const prepareMessages_anthropicReasoning = ({ messages, supportsAnthropicReasoningSignature }: { messages: LLMChatMessage[], supportsAnthropicReasoningSignature: boolean }) => {
|
|
||||||
const newMessages: InternalLLMChatMessage[] = []
|
|
||||||
for (const m of messages) {
|
|
||||||
if (m.role !== 'assistant') {
|
|
||||||
newMessages.push(m)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let newMessage: InternalLLMChatMessage
|
|
||||||
if (supportsAnthropicReasoningSignature && m.anthropicReasoning) {
|
|
||||||
const content = m.content ? [...m.anthropicReasoning, { type: 'text' as const, text: m.content }] : m.anthropicReasoning
|
|
||||||
newMessage = { role: 'assistant', content: content }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newMessage = { role: 'assistant', content: m.content }
|
|
||||||
}
|
|
||||||
newMessages.push(newMessage)
|
|
||||||
}
|
|
||||||
return { messages: newMessages }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// do this at end
|
|
||||||
const prepareMessages_noEmptyMessage = ({ messages }: { messages: InternalLLMChatMessage[] }): { messages: InternalLLMChatMessage[] } => {
|
|
||||||
for (const currMsg of messages) {
|
|
||||||
// if content is a string, replace string with empty msg
|
|
||||||
if (typeof currMsg.content === 'string')
|
|
||||||
currMsg.content = currMsg.content || EMPTY_MESSAGE
|
|
||||||
else {
|
|
||||||
// if content is an array, replace any empty text entries with empty msg, and make sure there's at least 1 entry
|
|
||||||
for (const c of currMsg.content) {
|
|
||||||
if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE
|
|
||||||
}
|
|
||||||
if (currMsg.content.length === 0) currMsg.content = [{ type: 'text', text: EMPTY_MESSAGE }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { messages }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- CHAT ---
|
|
||||||
|
|
||||||
export const prepareMessages = ({
|
|
||||||
messages,
|
|
||||||
aiInstructions,
|
|
||||||
supportsSystemMessage,
|
|
||||||
supportsAnthropicReasoningSignature,
|
|
||||||
contextWindow,
|
|
||||||
maxOutputTokens,
|
|
||||||
}: {
|
|
||||||
messages: LLMChatMessage[],
|
|
||||||
aiInstructions: string,
|
|
||||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
|
|
||||||
supportsAnthropicReasoningSignature: boolean,
|
|
||||||
contextWindow: number,
|
|
||||||
maxOutputTokens: number | null | undefined,
|
|
||||||
}) => {
|
|
||||||
maxOutputTokens = maxOutputTokens ?? 4_096 // default to 4096
|
|
||||||
|
|
||||||
const { messages: messages0 } = prepareMessages_normalize({ messages })
|
|
||||||
const { messages: messages1 } = prepareMessages_fitIntoContext({ messages: messages0, contextWindow, maxOutputTokens })
|
|
||||||
const { messages: messages2 } = prepareMessages_anthropicReasoning({ messages: messages1, supportsAnthropicReasoningSignature })
|
|
||||||
const { messages: messages3, separateSystemMessageStr } = prepareMessages_addSystemInstructions({ messages: messages2, aiInstructions, supportsSystemMessage })
|
|
||||||
const { messages: messages4 } = prepareMessages_noEmptyMessage({ messages: messages3 })
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages: messages4 as any,
|
|
||||||
separateSystemMessageStr
|
|
||||||
} as const
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- FIM ---
|
|
||||||
|
|
||||||
export const prepareFIMMessage = ({
|
|
||||||
messages,
|
|
||||||
aiInstructions,
|
|
||||||
}: {
|
|
||||||
messages: LLMFIMMessage,
|
|
||||||
aiInstructions: string,
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
let prefix = `\
|
|
||||||
${!aiInstructions ? '' : `\
|
|
||||||
// Instructions:
|
|
||||||
// Do not output an explanation. Try to avoid outputting comments. Only output the middle code.
|
|
||||||
${aiInstructions.split('\n').map(line => `//${line}`).join('\n')}`}
|
|
||||||
|
|
||||||
${messages.prefix}`
|
|
||||||
|
|
||||||
const suffix = messages.suffix
|
|
||||||
const stopTokens = messages.stopTokens
|
|
||||||
const ret = { prefix, suffix, stopTokens, maxTokens: 300 } as const
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Gemini has this, but they're openai-compat so we don't need to implement this
|
|
||||||
gemini request:
|
|
||||||
{ "role": "assistant",
|
|
||||||
"content": null,
|
|
||||||
"function_call": {
|
|
||||||
"name": "get_weather",
|
|
||||||
"arguments": {
|
|
||||||
"latitude": 48.8566,
|
|
||||||
"longitude": 2.3522
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gemini response:
|
|
||||||
{ "role": "assistant",
|
|
||||||
"function_response": {
|
|
||||||
"name": "get_weather",
|
|
||||||
"response": {
|
|
||||||
"temperature": "15°C",
|
|
||||||
"condition": "Cloudy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,15 +10,14 @@ import { MistralCore } from '@mistralai/mistralai/core.js';
|
||||||
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
||||||
|
|
||||||
|
|
||||||
import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js';
|
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
|
||||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||||
import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js';
|
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js';
|
||||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings } from '../../common/modelCapabilities.js';
|
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
|
||||||
import { extractReasoningWrapper, extractToolsWrapper } from './extractGrammar.js';
|
import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js';
|
||||||
|
|
||||||
|
|
||||||
type InternalCommonMessageParams = {
|
type InternalCommonMessageParams = {
|
||||||
aiInstructions: string;
|
|
||||||
onText: OnText;
|
onText: OnText;
|
||||||
onFinalMessage: OnFinalMessage;
|
onFinalMessage: OnFinalMessage;
|
||||||
onError: OnError;
|
onError: OnError;
|
||||||
|
|
@ -29,8 +28,8 @@ type InternalCommonMessageParams = {
|
||||||
_setAborter: (aborter: () => void) => void;
|
_setAborter: (aborter: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendChatParams_Internal = InternalCommonMessageParams & { messages: LLMChatMessage[]; chatMode: ChatMode | null; }
|
type SendChatParams_Internal = InternalCommonMessageParams & { messages: LLMChatMessage[]; separateSystemMessage: string | undefined; chatMode: ChatMode | null; }
|
||||||
type SendFIMParams_Internal = InternalCommonMessageParams & { messages: LLMFIMMessage; }
|
type SendFIMParams_Internal = InternalCommonMessageParams & { messages: LLMFIMMessage; separateSystemMessage: string | undefined; }
|
||||||
export type ListParams_Internal<ModelResponse> = ModelListParams<ModelResponse>
|
export type ListParams_Internal<ModelResponse> = ModelListParams<ModelResponse>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -96,7 +95,7 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, }: SendFIMParams_Internal) => {
|
const _sendOpenAICompatibleFIM = ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, }: SendFIMParams_Internal) => {
|
||||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
||||||
if (!supportsFIM) {
|
if (!supportsFIM) {
|
||||||
if (modelName === modelName_)
|
if (modelName === modelName_)
|
||||||
|
|
@ -106,16 +105,14 @@ const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = prepareFIMMessage({ messages: messages_, aiInstructions, })
|
|
||||||
|
|
||||||
const openai = newOpenAICompatibleSDK({ providerName, settingsOfProvider })
|
const openai = newOpenAICompatibleSDK({ providerName, settingsOfProvider })
|
||||||
openai.completions
|
openai.completions
|
||||||
.create({
|
.create({
|
||||||
model: modelName,
|
model: modelName,
|
||||||
prompt: messages.prefix,
|
prompt: prefix,
|
||||||
suffix: messages.suffix,
|
suffix: suffix,
|
||||||
stop: messages.stopTokens,
|
stop: stopTokens,
|
||||||
max_tokens: messages.maxTokens,
|
max_tokens: 300,
|
||||||
})
|
})
|
||||||
.then(async response => {
|
.then(async response => {
|
||||||
const fullText = response.choices[0]?.text
|
const fullText = response.choices[0]?.text
|
||||||
|
|
@ -128,14 +125,61 @@ const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => {
|
||||||
|
const { name, description, params } = toolInfo
|
||||||
|
return {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: name,
|
||||||
|
// strict: true, // strict mode - https://platform.openai.com/docs/guides/function-calling?api-mode=chat
|
||||||
|
description: description,
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: params,
|
||||||
|
// required: Object.keys(params), // in strict mode, all params are required and additionalProperties is false
|
||||||
|
// additionalProperties: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} satisfies OpenAI.Chat.Completions.ChatCompletionTool
|
||||||
|
}
|
||||||
|
|
||||||
|
const openAITools = (chatMode: ChatMode) => {
|
||||||
|
const allowedTools = availableTools(chatMode)
|
||||||
|
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
|
||||||
|
|
||||||
|
const openAITools: OpenAI.Chat.Completions.ChatCompletionTool[] = []
|
||||||
|
for (const t in allowedTools ?? {}) {
|
||||||
|
openAITools.push(toOpenAICompatibleTool(allowedTools[t]))
|
||||||
|
}
|
||||||
|
return openAITools
|
||||||
|
}
|
||||||
|
|
||||||
|
const openAIToolToRawToolCallObj = (name: string, toolParamsStr: string, id: string): RawToolCallObj | null => {
|
||||||
|
if (!isAToolName(name)) return null
|
||||||
|
const rawParams: RawToolParamsObj = {}
|
||||||
|
let input: unknown
|
||||||
|
try {
|
||||||
|
input = JSON.parse(toolParamsStr)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (input === null) return null
|
||||||
|
if (typeof input !== 'object') return null
|
||||||
|
for (const paramName in voidTools[name].params) {
|
||||||
|
rawParams[paramName as ToolParamName] = (input as any)[paramName]
|
||||||
|
}
|
||||||
|
return { id, name, rawParams, doneParams: Object.keys(rawParams) as ToolParamName[], isDone: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, aiInstructions, chatMode }: SendChatParams_Internal) => {
|
// ------------ OPENAI-COMPATIBLE ------------
|
||||||
|
|
||||||
|
|
||||||
|
const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage }: SendChatParams_Internal) => {
|
||||||
const {
|
const {
|
||||||
modelName,
|
modelName,
|
||||||
supportsSystemMessage,
|
specialToolFormat,
|
||||||
contextWindow,
|
|
||||||
maxOutputTokens,
|
|
||||||
reasoningCapabilities,
|
reasoningCapabilities,
|
||||||
} = getModelCapabilities(providerName, modelName_)
|
} = getModelCapabilities(providerName, modelName_)
|
||||||
|
|
||||||
|
|
@ -146,16 +190,17 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
||||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
||||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||||
|
|
||||||
// max tokens
|
// tools
|
||||||
const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
|
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
|
||||||
|
const nativeToolsObj = potentialTools ? { tools: potentialTools } as const : {}
|
||||||
|
|
||||||
// instance
|
// instance
|
||||||
const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsAnthropicReasoningSignature: false, contextWindow, maxOutputTokens: maxTokens })
|
|
||||||
const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
|
const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
|
||||||
const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||||
model: modelName,
|
model: modelName,
|
||||||
messages: messages,
|
messages: messages as any,
|
||||||
stream: true,
|
stream: true,
|
||||||
|
...nativeToolsObj,
|
||||||
// max_completion_tokens: maxTokens,
|
// max_completion_tokens: maxTokens,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,9 +213,9 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
||||||
onFinalMessage = newOnFinalMessage
|
onFinalMessage = newOnFinalMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// manually parse out tool results
|
// manually parse out tool results if XML
|
||||||
if (chatMode) {
|
if (!specialToolFormat) {
|
||||||
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
|
const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode)
|
||||||
onText = newOnText
|
onText = newOnText
|
||||||
onFinalMessage = newOnFinalMessage
|
onFinalMessage = newOnFinalMessage
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +223,10 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
||||||
let fullReasoningSoFar = ''
|
let fullReasoningSoFar = ''
|
||||||
let fullTextSoFar = ''
|
let fullTextSoFar = ''
|
||||||
|
|
||||||
|
let toolName = ''
|
||||||
|
let toolId = ''
|
||||||
|
let toolParamsStr = ''
|
||||||
|
|
||||||
openai.chat.completions
|
openai.chat.completions
|
||||||
.create(options)
|
.create(options)
|
||||||
.then(async response => {
|
.then(async response => {
|
||||||
|
|
@ -188,6 +237,17 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
||||||
const newText = chunk.choices[0]?.delta?.content ?? ''
|
const newText = chunk.choices[0]?.delta?.content ?? ''
|
||||||
fullTextSoFar += newText
|
fullTextSoFar += newText
|
||||||
|
|
||||||
|
// tool call
|
||||||
|
for (const tool of chunk.choices[0]?.delta?.tool_calls ?? []) {
|
||||||
|
const index = tool.index
|
||||||
|
if (index !== 0) continue
|
||||||
|
|
||||||
|
toolName += tool.function?.name ?? ''
|
||||||
|
toolParamsStr += tool.function?.arguments ?? '';
|
||||||
|
toolId += tool.id ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// reasoning
|
// reasoning
|
||||||
let newReasoning = ''
|
let newReasoning = ''
|
||||||
if (nameOfReasoningFieldInDelta) {
|
if (nameOfReasoningFieldInDelta) {
|
||||||
|
|
@ -199,11 +259,13 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
||||||
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
|
||||||
}
|
}
|
||||||
// on final
|
// on final
|
||||||
if (!fullTextSoFar && !fullReasoningSoFar) {
|
if (!fullTextSoFar && !fullReasoningSoFar && !toolName) {
|
||||||
onError({ message: 'Void: Response from model was empty.', fullError: null })
|
onError({ message: 'Void: Response from model was empty.', fullError: null })
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null });
|
const toolCall = openAIToolToRawToolCallObj(toolName, toolParamsStr, toolId)
|
||||||
|
const toolCallObj = toolCall ? { toolCall } : {}
|
||||||
|
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null, ...toolCallObj });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// when error/fail - this catches errors of both .create() and .then(for await)
|
// when error/fail - this catches errors of both .create() and .then(for await)
|
||||||
|
|
@ -251,14 +313,48 @@ const _openaiCompatibleList = async ({ onSuccess: onSuccess_, onError: onError_,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ------------ ANTHROPIC (HELPERS) ------------
|
||||||
|
const toAnthropicTool = (toolInfo: InternalToolInfo) => {
|
||||||
|
const { name, description, params } = toolInfo
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
input_schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: params,
|
||||||
|
// required: Object.keys(params),
|
||||||
|
},
|
||||||
|
} satisfies Anthropic.Messages.Tool
|
||||||
|
}
|
||||||
|
|
||||||
|
const anthropicTools = (chatMode: ChatMode) => {
|
||||||
|
const allowedTools = availableTools(chatMode)
|
||||||
|
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
|
||||||
|
|
||||||
|
const anthropicTools: Anthropic.Messages.ToolUnion[] = []
|
||||||
|
for (const t in allowedTools ?? {}) {
|
||||||
|
anthropicTools.push(toAnthropicTool(allowedTools[t]))
|
||||||
|
}
|
||||||
|
return anthropicTools
|
||||||
|
}
|
||||||
|
|
||||||
|
const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBlock): RawToolCallObj | null => {
|
||||||
|
const { id, name, input } = toolBlock
|
||||||
|
if (!isAToolName(name)) return null
|
||||||
|
const rawParams: RawToolParamsObj = {}
|
||||||
|
if (input === null) return null
|
||||||
|
if (typeof input !== 'object') return null
|
||||||
|
for (const paramName in voidTools[name].params) {
|
||||||
|
rawParams[paramName as ToolParamName] = (input as any)[paramName]
|
||||||
|
}
|
||||||
|
return { id, name, rawParams, doneParams: Object.keys(rawParams) as ToolParamName[], isDone: true }
|
||||||
|
}
|
||||||
|
|
||||||
// ------------ ANTHROPIC ------------
|
// ------------ ANTHROPIC ------------
|
||||||
const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, aiInstructions, chatMode }: SendChatParams_Internal) => {
|
const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => {
|
||||||
const {
|
const {
|
||||||
modelName,
|
modelName,
|
||||||
supportsSystemMessage,
|
specialToolFormat,
|
||||||
contextWindow,
|
|
||||||
maxOutputTokens,
|
|
||||||
reasoningCapabilities,
|
|
||||||
} = getModelCapabilities(providerName, modelName_)
|
} = getModelCapabilities(providerName, modelName_)
|
||||||
|
|
||||||
const thisConfig = settingsOfProvider.anthropic
|
const thisConfig = settingsOfProvider.anthropic
|
||||||
|
|
@ -269,26 +365,32 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||||
|
|
||||||
// anthropic-specific - max tokens
|
// anthropic-specific - max tokens
|
||||||
const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
|
const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled })
|
||||||
|
|
||||||
|
// tools
|
||||||
|
const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null
|
||||||
|
const nativeToolsObj = potentialTools ? { tools: potentialTools, tool_choice: { type: 'auto' } } as const : {}
|
||||||
|
|
||||||
|
|
||||||
// instance
|
// instance
|
||||||
const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsAnthropicReasoningSignature: true, contextWindow, maxOutputTokens: maxTokens })
|
|
||||||
const anthropic = new Anthropic({
|
const anthropic = new Anthropic({
|
||||||
apiKey: thisConfig.apiKey,
|
apiKey: thisConfig.apiKey,
|
||||||
dangerouslyAllowBrowser: true
|
dangerouslyAllowBrowser: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const stream = anthropic.messages.stream({
|
const stream = anthropic.messages.stream({
|
||||||
system: separateSystemMessageStr,
|
system: separateSystemMessage ?? undefined,
|
||||||
messages: messages,
|
messages: messages as AnthropicLLMChatMessage[],
|
||||||
model: modelName,
|
model: modelName,
|
||||||
max_tokens: maxTokens ?? 4_096, // anthropic requires this
|
max_tokens: maxTokens ?? 4_096, // anthropic requires this
|
||||||
...includeInPayload,
|
...includeInPayload,
|
||||||
|
...nativeToolsObj,
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// manually parse out tool results
|
// manually parse out tool results
|
||||||
if (chatMode) {
|
if (!specialToolFormat) {
|
||||||
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
|
const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode)
|
||||||
onText = newOnText
|
onText = newOnText
|
||||||
onFinalMessage = newOnFinalMessage
|
onFinalMessage = newOnFinalMessage
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +399,8 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
let fullText = ''
|
let fullText = ''
|
||||||
let fullReasoning = ''
|
let fullReasoning = ''
|
||||||
|
|
||||||
// let fullToolName = ''
|
let fullToolName = ''
|
||||||
// let fullToolParams = ''
|
let fullToolParams = ''
|
||||||
|
|
||||||
// there are no events for tool_use, it comes in at the end
|
// there are no events for tool_use, it comes in at the end
|
||||||
stream.on('streamEvent', e => {
|
stream.on('streamEvent', e => {
|
||||||
|
|
@ -320,10 +422,10 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
fullReasoning += '[redacted_thinking]'
|
fullReasoning += '[redacted_thinking]'
|
||||||
onText({ fullText, fullReasoning, })
|
onText({ fullText, fullReasoning, })
|
||||||
}
|
}
|
||||||
// else if (e.content_block.type === 'tool_use') {
|
else if (e.content_block.type === 'tool_use') {
|
||||||
// fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
|
fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
|
||||||
// onText({ fullText, fullReasoning, })
|
onText({ fullText, fullReasoning, })
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delta
|
// delta
|
||||||
|
|
@ -336,17 +438,20 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
fullReasoning += e.delta.thinking
|
fullReasoning += e.delta.thinking
|
||||||
onText({ fullText, fullReasoning, })
|
onText({ fullText, fullReasoning, })
|
||||||
}
|
}
|
||||||
// else if (e.delta.type === 'input_json_delta') { // tool use
|
else if (e.delta.type === 'input_json_delta') { // tool use
|
||||||
// fullToolParams += e.delta.partial_json ?? '' // anthropic gives us the partial delta (string) here - https://docs.anthropic.com/en/api/messages-streaming
|
fullToolParams += e.delta.partial_json ?? '' // anthropic gives us the partial delta (string) here - https://docs.anthropic.com/en/api/messages-streaming
|
||||||
// onText({ fullText, fullReasoning, })
|
onText({ fullText, fullReasoning, })
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// on done - (or when error/fail) - this is called AFTER last streamEvent
|
// on done - (or when error/fail) - this is called AFTER last streamEvent
|
||||||
stream.on('finalMessage', (response) => {
|
stream.on('finalMessage', (response) => {
|
||||||
const anthropicReasoning = response.content.filter(c => c.type === 'thinking' || c.type === 'redacted_thinking')
|
const anthropicReasoning = response.content.filter(c => c.type === 'thinking' || c.type === 'redacted_thinking')
|
||||||
onFinalMessage({ fullText, fullReasoning, anthropicReasoning })
|
const tools = response.content.filter(c => c.type === 'tool_use')
|
||||||
|
const toolCall = tools[0] && anthropicToolToRawToolCallObj(tools[0])
|
||||||
|
const toolCallObj = toolCall ? { toolCall } : {}
|
||||||
|
onFinalMessage({ fullText, fullReasoning, anthropicReasoning, ...toolCallObj })
|
||||||
})
|
})
|
||||||
// on error
|
// on error
|
||||||
stream.on('error', (error) => {
|
stream.on('error', (error) => {
|
||||||
|
|
@ -360,7 +465,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
|
|
||||||
// ------------ MISTRAL ------------
|
// ------------ MISTRAL ------------
|
||||||
// https://docs.mistral.ai/api/#tag/fim
|
// https://docs.mistral.ai/api/#tag/fim
|
||||||
const sendMistralFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions }: SendFIMParams_Internal) => {
|
const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName }: SendFIMParams_Internal) => {
|
||||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
||||||
if (!supportsFIM) {
|
if (!supportsFIM) {
|
||||||
if (modelName === modelName_)
|
if (modelName === modelName_)
|
||||||
|
|
@ -369,7 +474,6 @@ const sendMistralFIM = ({ messages: messages_, onFinalMessage, onError, settings
|
||||||
onError({ message: `Model ${modelName_} (${modelName}) does not support FIM.`, fullError: null })
|
onError({ message: `Model ${modelName_} (${modelName}) does not support FIM.`, fullError: null })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const messages = prepareFIMMessage({ messages: messages_, aiInstructions })
|
|
||||||
|
|
||||||
const mistral = new MistralCore({ apiKey: settingsOfProvider.mistral.apiKey })
|
const mistral = new MistralCore({ apiKey: settingsOfProvider.mistral.apiKey })
|
||||||
fimComplete(mistral,
|
fimComplete(mistral,
|
||||||
|
|
@ -378,7 +482,7 @@ const sendMistralFIM = ({ messages: messages_, onFinalMessage, onError, settings
|
||||||
prompt: messages.prefix,
|
prompt: messages.prefix,
|
||||||
suffix: messages.suffix,
|
suffix: messages.suffix,
|
||||||
stream: false,
|
stream: false,
|
||||||
maxTokens: messages.maxTokens,
|
maxTokens: 300,
|
||||||
stop: messages.stopTokens,
|
stop: messages.stopTokens,
|
||||||
})
|
})
|
||||||
.then(async response => {
|
.then(async response => {
|
||||||
|
|
@ -426,12 +530,10 @@ const ollamaList = async ({ onSuccess: onSuccess_, onError: onError_, settingsOf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendOllamaFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName, aiInstructions, _setAborter }: SendFIMParams_Internal) => {
|
const sendOllamaFIM = ({ messages, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }: SendFIMParams_Internal) => {
|
||||||
const thisConfig = settingsOfProvider.ollama
|
const thisConfig = settingsOfProvider.ollama
|
||||||
const ollama = newOllamaSDK({ endpoint: thisConfig.endpoint })
|
const ollama = newOllamaSDK({ endpoint: thisConfig.endpoint })
|
||||||
|
|
||||||
const messages = prepareFIMMessage({ messages: messages_, aiInstructions, })
|
|
||||||
|
|
||||||
let fullText = ''
|
let fullText = ''
|
||||||
ollama.generate({
|
ollama.generate({
|
||||||
model: modelName,
|
model: modelName,
|
||||||
|
|
@ -439,7 +541,7 @@ const sendOllamaFIM = ({ messages: messages_, onFinalMessage, onError, settingsO
|
||||||
suffix: messages.suffix,
|
suffix: messages.suffix,
|
||||||
options: {
|
options: {
|
||||||
stop: messages.stopTokens,
|
stop: messages.stopTokens,
|
||||||
num_predict: messages.maxTokens, // max tokens
|
num_predict: 300, // max tokens
|
||||||
// repeat_penalty: 1,
|
// repeat_penalty: 1,
|
||||||
},
|
},
|
||||||
raw: true,
|
raw: true,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { sendLLMMessageToProviderImplementation } from './sendLLMMessage.impl.js
|
||||||
|
|
||||||
export const sendLLMMessage = ({
|
export const sendLLMMessage = ({
|
||||||
messagesType,
|
messagesType,
|
||||||
aiInstructions,
|
|
||||||
messages: messages_,
|
messages: messages_,
|
||||||
onText: onText_,
|
onText: onText_,
|
||||||
onFinalMessage: onFinalMessage_,
|
onFinalMessage: onFinalMessage_,
|
||||||
|
|
@ -22,6 +21,7 @@ export const sendLLMMessage = ({
|
||||||
modelSelection,
|
modelSelection,
|
||||||
modelSelectionOptions,
|
modelSelectionOptions,
|
||||||
chatMode,
|
chatMode,
|
||||||
|
separateSystemMessage,
|
||||||
}: SendLLMMessageParams,
|
}: SendLLMMessageParams,
|
||||||
|
|
||||||
metricsService: IMetricsService
|
metricsService: IMetricsService
|
||||||
|
|
@ -108,12 +108,12 @@ export const sendLLMMessage = ({
|
||||||
}
|
}
|
||||||
const { sendFIM, sendChat } = implementation
|
const { sendFIM, sendChat } = implementation
|
||||||
if (messagesType === 'chatMessages') {
|
if (messagesType === 'chatMessages') {
|
||||||
sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, aiInstructions, chatMode })
|
sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage, chatMode })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (messagesType === 'FIMMessage') {
|
if (messagesType === 'FIMMessage') {
|
||||||
if (sendFIM) {
|
if (sendFIM) {
|
||||||
sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, aiInstructions })
|
sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, separateSystemMessage })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onError({ message: `Error: This provider does not support Autocomplete yet.`, fullError: null })
|
onError({ message: `Error: This provider does not support Autocomplete yet.`, fullError: null })
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue