mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
Merge pull request #312 from voideditor/model-selection
Tool use improvements + Clear settings
This commit is contained in:
commit
4212e50ac5
21 changed files with 456 additions and 380 deletions
|
|
@ -125,7 +125,7 @@ import { IMetricsService } from '../../workbench/contrib/void/common/metricsServ
|
|||
import { IVoidUpdateService } from '../../workbench/contrib/void/common/voidUpdateService.js';
|
||||
import { MetricsMainService } from '../../workbench/contrib/void/electron-main/metricsMainService.js';
|
||||
import { VoidMainUpdateService } from '../../workbench/contrib/void/electron-main/voidUpdateMainService.js';
|
||||
import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/llmMessageChannel.js';
|
||||
import { LLMMessageChannel } from '../../workbench/contrib/void/electron-main/sendLLMMessageChannel.js';
|
||||
|
||||
/**
|
||||
* The main VS Code application. There will only ever be one instance,
|
||||
|
|
@ -1256,8 +1256,8 @@ export class CodeApplication extends Disposable {
|
|||
const voidUpdatesChannel = ProxyChannel.fromService(accessor.get(IVoidUpdateService), disposables);
|
||||
mainProcessElectronServer.registerChannel('void-channel-update', voidUpdatesChannel);
|
||||
|
||||
const llmMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
|
||||
mainProcessElectronServer.registerChannel('void-channel-llmMessageService', llmMessageChannel);
|
||||
const sendLLMMessageChannel = new LLMMessageChannel(accessor.get(IMetricsService));
|
||||
mainProcessElectronServer.registerChannel('void-channel-llmMessage', sendLLMMessageChannel);
|
||||
|
||||
// Extension Host Debug Broadcasting
|
||||
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ import { EditorResourceAccessor } from '../../../common/editor.js';
|
|||
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
|
||||
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { ILLMMessageService } from '../common/llmMessageService.js';
|
||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||
import { isWindows } from '../../../../base/common/platform.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { FeatureName } from '../common/voidSettingsTypes.js';
|
||||
// import { IContextGatheringService } from './contextGatheringService.js';
|
||||
|
||||
|
||||
|
|
@ -767,8 +769,6 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
|
||||
|
||||
|
||||
// console.log('B')
|
||||
|
||||
// create a new autocompletion and add it to cache
|
||||
const newAutocompletion: Autocompletion = {
|
||||
id: this._autocompletionId++,
|
||||
|
|
@ -788,8 +788,14 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
|
||||
console.log('starting autocomplete...', predictionType)
|
||||
|
||||
const featureName: FeatureName = 'Autocomplete'
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
const isEnabled = this._settingsService.state.globalSettings.enableAutocomplete
|
||||
|
||||
// set parameters of `newAutocompletion` appropriately
|
||||
newAutocompletion.llmPromise = new Promise((resolve, reject) => {
|
||||
newAutocompletion.llmPromise = isEnabled ? new Promise((resolve, reject) => reject('Autocomplete is disabled')) : new Promise((resolve, reject) => {
|
||||
|
||||
const requestId = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'FIMMessage',
|
||||
|
|
@ -798,7 +804,8 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
suffix: llmSuffix,
|
||||
stopTokens: stopTokens,
|
||||
},
|
||||
useProviderFor: 'Autocomplete',
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
logging: { loggingName: 'Autocomplete' },
|
||||
onText: () => { }, // unused in FIMMessage
|
||||
// onText: async ({ fullText, newText }) => {
|
||||
|
|
@ -882,6 +889,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
|
|||
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
// @IContextGatheringService private readonly _contextGatheringService: IContextGatheringService,
|
||||
) {
|
||||
super()
|
||||
|
|
|
|||
|
|
@ -11,15 +11,16 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
|||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { ILLMMessageService } from '../common/llmMessageService.js';
|
||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||
import { chat_userMessageContent, chat_systemMessage, chat_lastUserMessageWithFilesAdded, chat_selectionsString } from './prompt/prompts.js';
|
||||
import { InternalToolInfo, IToolsService, ToolCallParams, ToolResultType, ToolName, toolNamesThatRequireApproval, voidTools } from './toolsService.js';
|
||||
import { LLMChatMessage, toLLMChatMessage, ToolCallType } from '../common/llmMessageTypes.js';
|
||||
import { AnthropicReasoning, LLMChatMessage, ToolCallType } from '../common/sendLLMMessageTypes.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IVoidFileService } from '../common/voidFileService.js';
|
||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { getErrorMessage } from '../../../../base/common/errors.js';
|
||||
import { ChatMode } from '../common/voidSettingsTypes.js';
|
||||
import { ChatMode, FeatureName } from '../common/voidSettingsTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
|
||||
|
||||
const findLastIndex = <T>(arr: T[], condition: (t: T) => boolean): number => {
|
||||
|
|
@ -32,6 +33,28 @@ const findLastIndex = <T>(arr: T[], condition: (t: T) => boolean): number => {
|
|||
}
|
||||
|
||||
|
||||
|
||||
const toLLMChatMessages = (chatMessages: ChatMessage[]): LLMChatMessage[] => {
|
||||
const llmChatMessages: LLMChatMessage[] = []
|
||||
for (const c of chatMessages) {
|
||||
if (c.role === 'user') {
|
||||
llmChatMessages.push({ role: c.role, content: c.content })
|
||||
}
|
||||
else if (c.role === 'assistant')
|
||||
llmChatMessages.push({ role: c.role, content: c.content, anthropicReasoning: c.anthropicReasoning })
|
||||
else if (c.role === 'tool')
|
||||
llmChatMessages.push({ role: c.role, id: c.id, name: c.name, params: c.paramsStr, content: c.content })
|
||||
else if (c.role === 'tool_request') {
|
||||
// pass
|
||||
}
|
||||
else {
|
||||
throw new Error(`Role ${(c as any).role} not recognized.`)
|
||||
}
|
||||
}
|
||||
return llmChatMessages
|
||||
}
|
||||
|
||||
|
||||
// one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text)
|
||||
export type CodeSelection = {
|
||||
type: 'Selection';
|
||||
|
|
@ -73,9 +96,9 @@ export type ToolRequestApproval<T extends ToolName> = {
|
|||
|
||||
// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
|
||||
export type ChatMessage =
|
||||
{
|
||||
| {
|
||||
role: 'user';
|
||||
content: string | null; // content displayed to the LLM on future calls - allowed to be '', will be replaced with (empty)
|
||||
content: string; // content displayed to the LLM on future calls - allowed to be '', will be replaced with (empty)
|
||||
displayContent: string | null; // content displayed to user - allowed to be '', will be ignored
|
||||
selections: StagingSelectionItem[] | null; // the user's selection
|
||||
state: {
|
||||
|
|
@ -84,8 +107,10 @@ export type ChatMessage =
|
|||
}
|
||||
} | {
|
||||
role: 'assistant';
|
||||
content: string | null; // content received from LLM - allowed to be '', will be replaced with (empty)
|
||||
reasoning: string | null; // reasoning from the LLM, used for step-by-step thinking
|
||||
content: 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
|
||||
|
||||
anthropicReasoning: AnthropicReasoning[] | null; // anthropic reasoning
|
||||
}
|
||||
| ToolMessage<ToolName>
|
||||
| ToolRequestApproval<ToolName>
|
||||
|
|
@ -148,7 +173,11 @@ const newThreadObject = () => {
|
|||
} satisfies ChatThreads[string]
|
||||
}
|
||||
|
||||
export const THREAD_STORAGE_KEY = 'void.chatThreadStorage'
|
||||
|
||||
// past values:
|
||||
// 'void.chatThreadStorage'
|
||||
|
||||
export const THREAD_STORAGE_KEY = 'void.chatThreadStorageI'
|
||||
|
||||
|
||||
export interface IChatThreadService {
|
||||
|
|
@ -213,6 +242,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
@ILLMMessageService private readonly _llmMessageService: ILLMMessageService,
|
||||
@IToolsService private readonly _toolsService: IToolsService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
) {
|
||||
super()
|
||||
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
|
||||
|
|
@ -290,13 +320,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// ---------- streaming ----------
|
||||
|
||||
private _finishStreamingTextMessage = (threadId: string, options: { content: string, reasoning?: string }, error?: { message: string, fullError: Error | null }) => {
|
||||
// add assistant's message to chat history, and clear selection
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: options.content, reasoning: options.reasoning || null })
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, error })
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async editUserMessageAndStreamResponse({ userMessage, chatMode, messageIdx }: { userMessage: string, chatMode: ChatMode, messageIdx: number }) {
|
||||
|
|
@ -366,6 +389,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
: chatMode === 'agent' ? Object.keys(voidTools).map(toolName => voidTools[toolName as ToolName])
|
||||
: undefined)
|
||||
|
||||
// these settings should not change throughout the loop (eg anthropic breaks if you change its thinking mode and it's using tools)
|
||||
const featureName: FeatureName = 'Chat'
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
||||
// agent loop
|
||||
const agentLoop = async () => {
|
||||
|
||||
|
|
@ -384,7 +413,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
const awaitable = new Promise<void>((res, rej) => { res_ = res })
|
||||
|
||||
// replace last userMessage with userMessageFullContent (which contains all the files too)
|
||||
const messages_ = this.getCurrentThread().messages.map(m => (toLLMChatMessage(m))).filter(m => !!m)
|
||||
const messages_ = toLLMChatMessages(this.getCurrentThread().messages)
|
||||
const lastUserMsgIdx = findLastIndex(messages_, m => m.role === 'user')
|
||||
|
||||
if (lastUserMsgIdx === -1) throw new Error(`Void: No user message found.`) // should never be -1
|
||||
|
|
@ -396,24 +425,25 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
...messages_.slice(lastUserMsgIdx + 1, Infinity),
|
||||
]
|
||||
|
||||
|
||||
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'chatMessages',
|
||||
useProviderFor: 'Ctrl+L',
|
||||
logging: { loggingName: `Agent` },
|
||||
messages,
|
||||
|
||||
tools: tools,
|
||||
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
logging: { loggingName: `Agent` },
|
||||
onText: ({ fullText, fullReasoning }) => {
|
||||
this._setStreamState(threadId, { messageSoFar: fullText, reasoningSoFar: fullReasoning })
|
||||
},
|
||||
onFinalMessage: async ({ fullText, toolCalls, fullReasoning }) => {
|
||||
onFinalMessage: async ({ fullText, toolCalls, fullReasoning, anthropicReasoning }) => {
|
||||
|
||||
if ((toolCalls?.length ?? 0) === 0) {
|
||||
this._finishStreamingTextMessage(threadId, { content: fullText, reasoning: fullReasoning })
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined })
|
||||
}
|
||||
else {
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning || null })
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined }) // clear streaming message
|
||||
|
||||
// deal with the tool
|
||||
|
|
@ -428,14 +458,9 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// 1. validate tool params
|
||||
let toolParams: ToolCallParams[typeof toolName]
|
||||
try {
|
||||
console.log('A')
|
||||
|
||||
const params = await this._toolsService.validateParams[toolName](tool.paramsStr)
|
||||
console.log('B')
|
||||
|
||||
toolParams = params
|
||||
} catch (error) {
|
||||
console.log('ERR1')
|
||||
const errorMessage = getErrorMessage(error)
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, })
|
||||
res_()
|
||||
|
|
@ -444,22 +469,16 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// 2. if tool requires approval, await the approval
|
||||
if (toolNamesThatRequireApproval.has(toolName)) {
|
||||
console.log('C')
|
||||
|
||||
const voidToolId = generateUuid()
|
||||
console.log('D')
|
||||
const toolApprovalPromise = new Promise<void>((res, rej) => { this.resRejOfToolAwaitingApproval[voidToolId] = { res, rej } })
|
||||
console.log('E')
|
||||
this._addMessageToThread(threadId, { role: 'tool_request', name: toolName, params: toolParams, voidToolId: voidToolId })
|
||||
try {
|
||||
console.log('F')
|
||||
|
||||
await toolApprovalPromise
|
||||
// accepted tool
|
||||
}
|
||||
catch (e) {
|
||||
console.log('ERR2')
|
||||
|
||||
// TODO!!! test rejection
|
||||
// if (Math.random() > 0) throw new Error('TESTING')
|
||||
const errorMessage = 'Tool call was rejected by the user.'
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, })
|
||||
res_()
|
||||
|
|
@ -470,10 +489,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// 3. call the tool
|
||||
let toolResult: ToolResultType[typeof toolName]
|
||||
try {
|
||||
console.log('G')
|
||||
toolResult = await this._toolsService.callTool[toolName](toolParams as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here
|
||||
} catch (error) {
|
||||
console.log('ERR3')
|
||||
const errorMessage = getErrorMessage(error)
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, })
|
||||
res_()
|
||||
|
|
@ -483,11 +500,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// 4. stringify the result to give the LLM
|
||||
let toolResultStr: string
|
||||
try {
|
||||
|
||||
console.log('H')
|
||||
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
||||
// if (Math.random() > 0) throw new Error('This is not an allowed repo.')
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}`
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', value: errorMessage }, })
|
||||
|
|
@ -495,8 +508,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
return
|
||||
}
|
||||
|
||||
console.log('I')
|
||||
|
||||
// 5. add to history
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: toolResultStr, result: { type: 'success', params: toolParams, value: toolResult }, })
|
||||
res_()
|
||||
|
|
@ -506,20 +517,20 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
onError: (error) => {
|
||||
const messageSoFar = this.streamState[threadId]?.messageSoFar ?? ''
|
||||
const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? ''
|
||||
this._finishStreamingTextMessage(threadId, { content: messageSoFar, reasoning: reasoningSoFar }, error)
|
||||
// add assistant's message to chat history, and clear selection
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: messageSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, error })
|
||||
res_()
|
||||
},
|
||||
})
|
||||
if (llmCancelToken === null) break
|
||||
this._setStreamState(threadId, { streamingToken: llmCancelToken })
|
||||
|
||||
console.log('awaiting agentloop')
|
||||
await awaitable
|
||||
console.log('done')
|
||||
}
|
||||
}
|
||||
|
||||
agentLoop() // DO NOT AWAIT THIS, add fn should resolve when we've added message (this lets us interrupt the agent loop correctly instead of waiting for it to resolve)
|
||||
agentLoop()
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -528,7 +539,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken)
|
||||
const messageSoFar = this.streamState[threadId]?.messageSoFar ?? ''
|
||||
const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? ''
|
||||
this._finishStreamingTextMessage(threadId, { content: messageSoFar, reasoning: reasoningSoFar })
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: messageSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined })
|
||||
}
|
||||
|
||||
dismissStreamError(threadId: string): void {
|
||||
|
|
|
|||
|
|
@ -38,11 +38,13 @@ import { EditorOption } from '../../../../editor/common/config/editorOptions.js'
|
|||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { ILLMMessageService } from '../common/llmMessageService.js';
|
||||
import { LLMChatMessage, OnError, errorDetails } from '../common/llmMessageTypes.js';
|
||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||
import { LLMChatMessage, OnError, errorDetails } from '../common/sendLLMMessageTypes.js';
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { IVoidFileService } from '../common/voidFileService.js';
|
||||
import { IEditCodeService, URIStreamState, AddCtrlKOpts, StartApplyingOpts } from './editCodeServiceInterface.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { FeatureName } from '../common/voidSettingsTypes.js';
|
||||
|
||||
const configOfBG = (color: Color) => {
|
||||
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
||||
|
|
@ -268,6 +270,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IVoidFileService private readonly _voidFileService: IVoidFileService,
|
||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
|
@ -1188,20 +1191,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
// throws if there's an error
|
||||
public startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null {
|
||||
if (opts.type === 'rewrite') {
|
||||
const added = this._initializeWriteoverStream(opts)
|
||||
if (!added) return null
|
||||
const [diffZone, promise] = added
|
||||
return [diffZone._URI, promise]
|
||||
}
|
||||
else if (opts.type === 'searchReplace') {
|
||||
const added = this._initializeSearchAndReplaceStream(opts)
|
||||
if (!added) return null
|
||||
if (!added) return null
|
||||
const [diffZone, promise] = added
|
||||
return [diffZone._URI, promise]
|
||||
}
|
||||
return null
|
||||
let res: [DiffZone, Promise<void>] | undefined = undefined
|
||||
if (opts.type === 'rewrite') res = this._initializeWriteoverStream(opts)
|
||||
else if (opts.type === 'searchReplace') res = this._initializeSearchAndReplaceStream(opts)
|
||||
|
||||
if (!res) return null
|
||||
const [diffZone, applyDonePromise] = res
|
||||
return [diffZone._URI, applyDonePromise]
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1377,6 +1373,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
let fullTextSoFar = '' // so far (INCLUDING ignored suffix)
|
||||
let prevIgnoredSuffix = ''
|
||||
|
||||
const featureName: FeatureName = opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K'
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
const writeover = async () => {
|
||||
|
||||
let resMessageDonePromise: () => void = () => { }
|
||||
|
|
@ -1384,9 +1384,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'chatMessages',
|
||||
useProviderFor: opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K',
|
||||
logging: { loggingName: `Edit (Writeover) - ${from}` },
|
||||
messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
onText: (params) => {
|
||||
const { fullText: fullText_ } = params
|
||||
const newText_ = fullText_.substring(fullTextSoFar.length, Infinity)
|
||||
|
|
@ -1471,13 +1472,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
|
||||
// promise that resolves when the apply is done
|
||||
let resApplyPromise: () => void
|
||||
let rejApplyPromise: (e: any) => void
|
||||
const applyPromise = new Promise<void>((res_, rej_) => { resApplyPromise = res_; rejApplyPromise = rej_ })
|
||||
let resApplyDonePromise: () => void
|
||||
let rejApplyDonePromise: (e: any) => void
|
||||
const applyDonePromise = new Promise<void>((res_, rej_) => { resApplyDonePromise = res_; rejApplyDonePromise = rej_ })
|
||||
|
||||
// add to history
|
||||
const { onFinishEdit } = this._addToHistory(uri, {
|
||||
onUndo: () => { if (diffZone._streamState.isStreaming) rejApplyPromise(new Error('Edit was interrupted by pressing undo.')) }
|
||||
onUndo: () => { if (diffZone._streamState.isStreaming) rejApplyDonePromise(new Error('Edit was interrupted by pressing undo.')) }
|
||||
})
|
||||
|
||||
// TODO replace these with whatever block we're on initially if already started (caching apply)
|
||||
|
|
@ -1559,6 +1560,12 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
let oldBlocks: ExtractedSearchReplaceBlock[] = []
|
||||
|
||||
|
||||
const featureName: FeatureName = 'Apply'
|
||||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
|
||||
const retryLoop = async () => {
|
||||
// this generates >>>>>>> ORIGINAL <<<<<<< REPLACE blocks and and simultaneously applies it
|
||||
let shouldSendAnotherMessage = true
|
||||
|
|
@ -1569,13 +1576,14 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
nMessagesSent += 1
|
||||
|
||||
let resMessageDonePromise: () => void = () => { }
|
||||
const messageDonePromise = new Promise<void>((res_) => { resMessageDonePromise = res_ })
|
||||
const messageDonePromise = new Promise<void>((res, rej) => { resMessageDonePromise = res })
|
||||
|
||||
streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'chatMessages',
|
||||
useProviderFor: 'Apply',
|
||||
logging: { loggingName: `Edit (Search/Replace) - ${from}` },
|
||||
messages,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
onText: (params) => {
|
||||
const { fullText } = params
|
||||
// blocks are [done done done ... {writingFinal|writingOriginal}]
|
||||
|
|
@ -1613,11 +1621,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
if (typeof originalBounds === 'string') {
|
||||
const content = errMsgOfInvalidStr(originalBounds, block.orig)
|
||||
messages.push(
|
||||
{ role: 'assistant', content: fullText }, // latest output
|
||||
{ role: 'assistant', content: fullText, anthropicReasoning: null }, // latest output
|
||||
{ role: 'user', content: content } // user explanation of what's wrong
|
||||
)
|
||||
|
||||
if (streamRequestIdRef.current) this._llmMessageService.abort(streamRequestIdRef.current)
|
||||
|
||||
// REVERT
|
||||
const numLines = this._getNumLines(uri)
|
||||
|
|
@ -1638,7 +1645,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
oldBlocks = []
|
||||
addedTrackingZoneOfBlockNum.splice(0, Infinity) // clear the array
|
||||
|
||||
// abort and resolve
|
||||
shouldSendAnotherMessage = true
|
||||
if (streamRequestIdRef.current) this._llmMessageService.abort(streamRequestIdRef.current)
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
resMessageDonePromise()
|
||||
return
|
||||
|
|
@ -1750,9 +1759,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
} // end retryLoop
|
||||
|
||||
retryLoop().then(() => resApplyPromise()).catch((e) => rejApplyPromise(e))
|
||||
retryLoop().then(() => resApplyDonePromise()).catch((e) => rejApplyDonePromise(e))
|
||||
|
||||
return [diffZone, applyPromise]
|
||||
return [diffZone, applyDonePromise]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnText } from '../../common/llmMessageTypes.js'
|
||||
import { OnText } from '../../common/sendLLMMessageTypes.js'
|
||||
import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js'
|
||||
|
||||
class SurroundingsRemover {
|
||||
|
|
|
|||
|
|
@ -17,41 +17,46 @@ export const tripleTick = ['```', '```']
|
|||
|
||||
export const editToolDesc_toolDescription = `\
|
||||
A high level description of the change you'd like to make in the file. This description will be handed to a dumber, faster model that will quickly apply the change. \
|
||||
Typically the best description you can give here is a high level view of the final code you'd like to see. For example, code excerpt(s) with "// ... existing code ..." comments to help you write less. \
|
||||
Typically the best description you can give here is a high level view of the final code you'd like to see. For example, you can write code excerpt(s) with "// ... existing code ..." comments to help you write less. \
|
||||
However, you are allowed to describe the change using whatever text/language you like, especially if the change is better described without code. \
|
||||
Do NOT output the whole file if possible, and try to write as LITTLE as needed to describe the change.`
|
||||
|
||||
|
||||
|
||||
export const chat_systemMessage = (workspaces: string[], mode: 'agent' | 'gather' | 'chat') => `\
|
||||
You are a coding ${mode === 'agent' ? 'agent' : 'assistant'}. Your job is to help the user understand/${mode === 'agent' ? 'make' : 'suggest'} changes to their codebase.
|
||||
You will be given instructions to follow from the user, \`INSTRUCTIONS\`. You may also be given a list of selections that the user has specifically selected, \`SELECTIONS\`.
|
||||
You are a coding ${mode === 'agent' ? 'agent' : 'assistant'}. Your job is to help the user ${mode === 'agent' ? 'make changes to their codebase' : 'search and understand their codebase'}.
|
||||
You will be given instructions to follow from the user, \`INSTRUCTIONS\`. You may also be given a list of files that the user has specifically selected, \`SELECTIONS\`.
|
||||
Please assist the user with their query. The user's query is never invalid.
|
||||
|
||||
The user's system information is as follows:
|
||||
- ${os}
|
||||
- Open workspaces: ${workspaces.join(', ')}
|
||||
|
||||
${mode === 'agent' || mode === 'gather' ? `\
|
||||
${mode === 'agent' || mode === 'gather' /* tool use */ ? `\
|
||||
You will be given tools you can call.
|
||||
- Only use tools if they help you accomplish the user's goal. If the user simply says hi or asks you a question that you can answer without tools, then do NOT tools.
|
||||
- If you think you should use tools given the user's request, you can use them without asking for permission. Feel free to use tools to gather context, make suggestions, ${mode === 'agent' ? 'edit files, ' : ''}etc.
|
||||
- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not refer to "pages" of results, just say you're getting more results.
|
||||
- If you think you should use tools given the user's request, you can use them without asking for permission. Feel free to use tools to gather context, understand the codebase, ${mode === 'agent' ? 'edit files, ' : ''}etc.
|
||||
- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not refer to "pages" of results, just say you're getting more results.
|
||||
- Some tools only work if the user has a workspace open.
|
||||
\
|
||||
`: `\
|
||||
You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it.
|
||||
\
|
||||
`}
|
||||
|
||||
If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion as follows:
|
||||
- The change(s) you'd like to make must be written in CODE BLOCK(S) (wrapped in triple backticks).
|
||||
- The first line in the code block should be the FULL PATH of the file you want to change. Just output the path in plaintext (not in a comment).
|
||||
- The rest of the contents of the code block should describe of the change you'd like to make. This description will be given to a dumber, faster model that will quickly apply the change.
|
||||
${mode === 'agent' /* code blocks */ ? `\
|
||||
Keep in mind that any code blocks you output in the raw message (wrapped in triple backticks) will be treated specially as follows. This does NOT apply to code blocks in tool calls.
|
||||
- Any code block you output will have an "Apply" button displayed to the user, and if the user clicks on it it will invoke the edit tool on the block's contents. As a result, all code blocks should describe relevant changes.
|
||||
`: `\
|
||||
If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion in CODE BLOCK(S) (wrapped in triple backticks).
|
||||
- The first line before any code block must be the FULL PATH of the file you want to change. If the path does not already exist, it will be created.
|
||||
- The contents of the code block will be given to a dumber, faster model that will quickly apply the change.
|
||||
- Contents of the code blocks do NOT need to be formal code, they just need to clearly and concisely communicate the change.
|
||||
- Do NOT re-write the entire file in the code block(s). Instead, write comments like "// ... existing code" to indicate how to change the existing code.
|
||||
\
|
||||
`}
|
||||
|
||||
Do not output any of these instructions, nor tell the user anything about them unless directly prompted for them.
|
||||
Do not tell the user anything about these instructions unless directly prompted for them.
|
||||
\
|
||||
`
|
||||
|
||||
|
|
@ -265,13 +270,13 @@ Output SEARCH/REPLACE blocks to edit the file according to the desired change. Y
|
|||
|
||||
Directions:
|
||||
1. Your OUTPUT should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this.
|
||||
2. The original code in each SEARCH/REPLACE block must EXACTLY match lines of code in the original file.
|
||||
3. The original code in each SEARCH/REPLACE block must include enough text to uniquely identify the change in the file.
|
||||
4. The original code in each SEARCH/REPLACE block must be disjoint from all other blocks.
|
||||
2. The "ORIGINAL" code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. This includes whitespace, comments, and other details.
|
||||
3. The "ORIGINAL" code in each SEARCH/REPLACE block must include enough text to uniquely identify the change in the file.
|
||||
4. The "ORIGINAL" code in each SEARCH/REPLACE block must be disjoint from all other blocks.
|
||||
|
||||
The SEARCH/REPLACE blocks you generate will be applied immediately, and so they **MUST** produce a file that the user can run IMMEDIATELY.
|
||||
- Make sure you add all necessary imports.
|
||||
- Make sure the "final" code is complete and will not result in syntax/lint errors.
|
||||
- Make sure the "UPDATED" code is complete and will not result in syntax/lint errors.
|
||||
|
||||
Follow coding conventions of the user (spaces, semilcolons, comments, etc). If the user spaces or formats things a certain way, CONTINUE formatting it that way, even if you prefer otherwise.
|
||||
|
||||
|
|
@ -308,11 +313,7 @@ ORIGINAL_FILE
|
|||
${originalCode}
|
||||
|
||||
CHANGE
|
||||
${applyStr}
|
||||
|
||||
INSTRUCTIONS
|
||||
Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggested SEARCH/REPLACE blocks, without any explanation.
|
||||
`
|
||||
${applyStr}`
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ const getChatBubbleId = (threadId: string, messageIdx: number) => `${threadId}-$
|
|||
// const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
// const voidSettingsState = useSettingsState()
|
||||
|
||||
// const modelSelection = voidSettingsState.modelSelectionOfFeature['Ctrl+L']
|
||||
// const modelSelection = voidSettingsState.modelSelectionOfFeature['Chat']
|
||||
// if (!modelSelection) return null
|
||||
|
||||
// const { modelName, providerName } = modelSelection
|
||||
|
|
@ -210,13 +210,13 @@ const ReasoningOptionDropdown = () => {
|
|||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
const voidSettingsState = useSettingsState()
|
||||
|
||||
const modelSelection = voidSettingsState.modelSelectionOfFeature['Ctrl+L']
|
||||
const modelSelection = voidSettingsState.modelSelectionOfFeature['Chat']
|
||||
if (!modelSelection) return null
|
||||
|
||||
const { modelName, providerName } = modelSelection
|
||||
const { canToggleReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).supportsReasoning || {}
|
||||
|
||||
const { isReasoningEnabled } = getModelSelectionState(providerName, modelName, voidSettingsState.optionsOfModelSelection)
|
||||
const { isReasoningEnabled } = getModelSelectionState(providerName, modelName, voidSettingsState.optionsOfModelSelection[providerName]?.[modelName])
|
||||
|
||||
if (canToggleReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now)
|
||||
return null // unused right now
|
||||
|
|
@ -277,7 +277,6 @@ interface VoidChatAreaProps {
|
|||
divRef?: React.RefObject<HTMLDivElement>;
|
||||
|
||||
// UI customization
|
||||
featureName: FeatureName;
|
||||
className?: string;
|
||||
showModelDropdown?: boolean;
|
||||
showSelections?: boolean;
|
||||
|
|
@ -304,7 +303,6 @@ export const VoidChatArea: React.FC<VoidChatAreaProps> = ({
|
|||
isDisabled = false,
|
||||
className = '',
|
||||
showModelDropdown = true,
|
||||
featureName,
|
||||
showSelections = false,
|
||||
showProspectiveSelections = true,
|
||||
selections,
|
||||
|
|
@ -363,7 +361,7 @@ export const VoidChatArea: React.FC<VoidChatAreaProps> = ({
|
|||
{showModelDropdown && (
|
||||
<div className='max-w-[200px] flex-grow'>
|
||||
<ReasoningOptionDropdown />
|
||||
<ModelDropdown featureName={featureName} />
|
||||
<ModelDropdown featureName={'Chat'} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -727,8 +725,6 @@ const DropdownComponent = ({
|
|||
|
||||
const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubbleProps & { chatMessage: ChatMessage & { role: 'user' } }) => {
|
||||
|
||||
const role = chatMessage.role
|
||||
|
||||
const accessor = useAccessor()
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
|
||||
|
|
@ -758,7 +754,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble
|
|||
const _mustInitialize = useRef(true)
|
||||
const _justEnabledEdit = useRef(false)
|
||||
useEffect(() => {
|
||||
const canInitialize = role === 'user' && mode === 'edit' && textAreaRefState
|
||||
const canInitialize = mode === 'edit' && textAreaRefState
|
||||
const shouldInitialize = _justEnabledEdit.current || _mustInitialize.current
|
||||
if (canInitialize && shouldInitialize) {
|
||||
setStagingSelections(chatMessage.selections || [])
|
||||
|
|
@ -771,7 +767,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble
|
|||
_mustInitialize.current = false
|
||||
}
|
||||
|
||||
}, [chatMessage, role, mode, _justEnabledEdit, textAreaRefState, textAreaFnsRef.current, _justEnabledEdit.current, _mustInitialize.current])
|
||||
}, [chatMessage, mode, _justEnabledEdit, textAreaRefState, textAreaFnsRef.current, _justEnabledEdit.current, _mustInitialize.current])
|
||||
|
||||
const onOpenEdit = () => {
|
||||
setIsBeingEdited(true)
|
||||
|
|
@ -843,7 +839,6 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble
|
|||
isDisabled={isDisabled}
|
||||
showSelections={true}
|
||||
showProspectiveSelections={false}
|
||||
featureName="Ctrl+L"
|
||||
selections={stagingSelections}
|
||||
setSelections={setStagingSelections}
|
||||
>
|
||||
|
|
@ -893,7 +888,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isLoading }: ChatBubble
|
|||
</div>
|
||||
|
||||
|
||||
{role === 'user' && <EditSymbol
|
||||
{<EditSymbol
|
||||
size={18}
|
||||
className={`
|
||||
absolute -top-1 -right-1
|
||||
|
|
@ -931,11 +926,15 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx }: ChatB
|
|||
const hasReasoning = !!reasoningStr
|
||||
const thread = chatThreadsService.getCurrentThread()
|
||||
|
||||
|
||||
const chatMessageLocation: ChatMessageLocation = {
|
||||
threadId: thread.id,
|
||||
messageIdx: messageIdx,
|
||||
}
|
||||
|
||||
const isEmpty = !chatMessage.content && !chatMessage.reasoning
|
||||
if (isEmpty) return null
|
||||
|
||||
return <>
|
||||
|
||||
{/* reasoning token */}
|
||||
|
|
@ -1034,9 +1033,13 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
} } = {
|
||||
'read_file': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const { params } = toolRequest
|
||||
return <DropdownComponent title={title} desc1={getBasename(params.uri.toString())} icon={<Dot className={`stroke-orange-500`} />} />
|
||||
return <DropdownComponent title={title} desc1={getBasename(params.uri.toString())} icon={<Dot className={`stroke-orange-500`} />}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
/>
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -1198,9 +1201,13 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
},
|
||||
'delete_uri': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const { params } = toolRequest
|
||||
return <DropdownComponent title={title} desc1={getBasename(params.uri.fsPath) + ' (deleted)'} />
|
||||
return <DropdownComponent title={title} desc1={getBasename(params.uri.fsPath) + ' (deleted)'}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
/>
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -1218,9 +1225,15 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
},
|
||||
'edit': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const { params } = toolRequest
|
||||
return <DropdownComponent title={title} desc1={getBasename(params.uri.fsPath)} icon={<Dot className={`stroke-orange-500`} />} />
|
||||
return <DropdownComponent title={title} desc1={getBasename(params.uri.fsPath)} icon={<Dot className={`stroke-orange-500`} />}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
>
|
||||
<ChatMarkdownRender string={params.changeDescription} />
|
||||
</DropdownComponent>
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -1240,9 +1253,13 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
},
|
||||
'terminal_command': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const { params } = toolRequest
|
||||
return <DropdownComponent title={title} desc1={`"${params.command}"`} icon={<Dot className={`stroke-orange-500`} />} />
|
||||
return <DropdownComponent title={title} desc1={`"${params.command}"`} icon={<Dot className={`stroke-orange-500`} />}
|
||||
// TODO!!! open the terminal with that ID
|
||||
/>
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -1294,22 +1311,17 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: ChatBubbleProps) =>
|
|||
else if (role === 'tool_request') {
|
||||
const isLastMessage = true // TODO!!! fix this
|
||||
if (!isLastMessage) return null
|
||||
const ToolMessageComponent = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
|
||||
const ToolRequestComponent = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
|
||||
return <>
|
||||
<ToolMessageComponent
|
||||
toolRequest={chatMessage}
|
||||
/>
|
||||
<ToolRequestComponent toolRequest={chatMessage} />
|
||||
<ToolRequestAcceptRejectButtons toolRequest={chatMessage} />
|
||||
</>
|
||||
}
|
||||
else if (role === 'tool') {
|
||||
const title = toolNameToTitle[chatMessage.name]
|
||||
if (chatMessage.result.type === 'error') return <ToolError title={title} errorMessage={chatMessage.result.value} />
|
||||
|
||||
const ToolMessageComponent = toolNameToComponent[chatMessage.name].resultWrapper as React.FC<{ toolMessage: any }> // ts isnt smart enough...
|
||||
return <ToolMessageComponent
|
||||
toolMessage={chatMessage}
|
||||
/>
|
||||
const ToolResultComponent = toolNameToComponent[chatMessage.name].resultWrapper as React.FC<{ toolMessage: any }> // ts isnt smart enough...
|
||||
return <ToolResultComponent toolMessage={chatMessage} />
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1363,7 +1375,7 @@ export const SidebarChat = () => {
|
|||
const initVal = ''
|
||||
const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!initVal)
|
||||
|
||||
const isDisabled = instructionsAreEmpty || !!isFeatureNameDisabled('Ctrl+L', settingsState)
|
||||
const isDisabled = instructionsAreEmpty || !!isFeatureNameDisabled('Chat', settingsState)
|
||||
|
||||
const [sidebarRef, sidebarDimensions] = useResizeObserver()
|
||||
const [chatAreaRef, chatAreaDimensions] = useResizeObserver()
|
||||
|
|
@ -1382,6 +1394,9 @@ export const SidebarChat = () => {
|
|||
|
||||
// send message to LLM
|
||||
const userMessage = textAreaRef.current?.value ?? ''
|
||||
|
||||
// getModelCapabilities() // TODO!!! check if can go into agent mode
|
||||
|
||||
await chatThreadsService.addUserMessageAndStreamResponse({ userMessage, chatMode: 'agent' })
|
||||
|
||||
setSelections([]) // clear staging
|
||||
|
|
@ -1420,8 +1435,9 @@ export const SidebarChat = () => {
|
|||
<ChatBubble key={getChatBubbleId(currentThread.id, streamingChatIdx)}
|
||||
messageIdx={streamingChatIdx} chatMessage={{
|
||||
role: 'assistant',
|
||||
content: messageSoFar ?? null,
|
||||
reasoning: reasoningSoFar ?? null,
|
||||
content: messageSoFar ?? '',
|
||||
reasoning: reasoningSoFar ?? '',
|
||||
anthropicReasoning: null,
|
||||
}}
|
||||
isLoading={isStreaming}
|
||||
/> : null
|
||||
|
|
@ -1492,7 +1508,6 @@ export const SidebarChat = () => {
|
|||
selections={selections}
|
||||
setSelections={setSelections}
|
||||
onClickAnywhere={() => { textAreaRef.current?.focus() }}
|
||||
featureName="Ctrl+L"
|
||||
>
|
||||
<VoidInputBox2
|
||||
className='min-h-[81px] px-0.5'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { IContextViewService, IContextMenuService } from '../../../../../../../p
|
|||
import { IFileService } from '../../../../../../../platform/files/common/files.js';
|
||||
import { IHoverService } from '../../../../../../../platform/hover/browser/hover.js';
|
||||
import { IThemeService } from '../../../../../../../platform/theme/common/themeService.js';
|
||||
import { ILLMMessageService } from '../../../../../../../workbench/contrib/void/common/llmMessageService.js';
|
||||
import { ILLMMessageService } from '../../../../common/sendLLMMessageService.js';
|
||||
import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js';
|
||||
import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js';
|
||||
import { IEditCodeService, URIStreamState } from '../../../editCodeServiceInterface.js'
|
||||
|
|
|
|||
|
|
@ -366,15 +366,20 @@ export const AutoRefreshToggle = () => {
|
|||
// right now this is just `enabled_autoRefreshModels`
|
||||
const enabled = voidSettingsState.globalSettings[settingName]
|
||||
|
||||
return <SubtleButton
|
||||
onClick={() => {
|
||||
voidSettingsService.setGlobalSetting(settingName, !enabled)
|
||||
metricsService.capture('Click', { action: 'Autorefresh Toggle', settingName, enabled: !enabled })
|
||||
}}
|
||||
text={`Automatically detect local providers and models (${refreshableProviderNames.map(providerName => displayInfoOfProviderName(providerName).title).join(', ')}).`}
|
||||
icon={enabled ? <Check className='stroke-green-500 size-3' /> : <X className='stroke-red-500 size-3' />}
|
||||
disabled={false}
|
||||
/>
|
||||
return <div className='flex items-center px-3 gap-x-1.5'>
|
||||
<VoidSwitch
|
||||
size='xxs'
|
||||
value={enabled}
|
||||
onChange={(newVal) => {
|
||||
voidSettingsService.setGlobalSetting(settingName, newVal)
|
||||
metricsService.capture('Click', { action: 'Autorefresh Toggle', settingName, enabled: newVal })
|
||||
}} />
|
||||
|
||||
<span className='text-void-fg-3'>
|
||||
{`Automatically detect local providers and models (${refreshableProviderNames.map(providerName => displayInfoOfProviderName(providerName).title).join(', ')}).`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -459,7 +464,7 @@ export const FeaturesTab = () => {
|
|||
|
||||
<div className='w-full'>
|
||||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Apply')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 my-1'>We recommend the smartest model you{`'`}ve got, like Claude 3.7 or GPT 4o.</div>
|
||||
<div className='text-sm italic text-void-fg-3 my-1'>We recommend using Claude 3.7 or GPT 4o.</div>
|
||||
<ModelDropdown featureName={'Apply'} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -370,7 +370,6 @@ export class ToolsService implements IToolsService {
|
|||
},
|
||||
|
||||
edit: async (params: string) => {
|
||||
console.log('validating edit!!!')
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o
|
||||
const uri = validateURI(uriStr)
|
||||
|
|
@ -395,7 +394,7 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1)
|
||||
const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1
|
||||
const fileContents = readFileContents.slice(fromIdx, toIdx + 1) || '(empty)' // paginate
|
||||
const fileContents = readFileContents.slice(fromIdx, toIdx + 1) // paginate
|
||||
const hasNextPage = (readFileContents.length - 1) - toIdx >= 1
|
||||
return { fileContents, hasNextPage }
|
||||
},
|
||||
|
|
@ -446,16 +445,13 @@ export class ToolsService implements IToolsService {
|
|||
},
|
||||
|
||||
edit: async ({ uri, changeDescription }) => {
|
||||
console.log('editing!!!!')
|
||||
const [_, p] = editCodeService.startApplying({
|
||||
const [_, applyDonePromise] = editCodeService.startApplying({
|
||||
uri,
|
||||
applyStr: changeDescription,
|
||||
from: 'ClickApply',
|
||||
type: 'searchReplace',
|
||||
}) ?? []
|
||||
console.log('B')
|
||||
|
||||
await p
|
||||
await applyDonePromise
|
||||
return {}
|
||||
},
|
||||
terminal_command: async ({ command }) => {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import './voidUpdateActions.js'
|
|||
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
|
||||
|
||||
// llmMessage
|
||||
import '../common/llmMessageService.js'
|
||||
import '../common/sendLLMMessageService.js'
|
||||
|
||||
// voidSettings
|
||||
import '../common/voidSettingsService.js'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OptionsOfModelSelection, ProviderName } from './voidSettingsTypes.js';
|
||||
import { ModelSelectionOptions, ProviderName } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
export const defaultModelsOfProvider = {
|
||||
|
|
@ -633,11 +633,11 @@ export const getProviderCapabilities = (providerName: ProviderName) => {
|
|||
}
|
||||
|
||||
// state from optionsOfModelSelection
|
||||
export const getModelSelectionState = (providerName: ProviderName, modelName: string, optionsOfModelSelection: OptionsOfModelSelection): { isReasoningEnabled: boolean, reasoningBudget: number | undefined } => {
|
||||
const { canToggleReasoning } = getModelCapabilities(providerName, modelName).supportsReasoning || {}
|
||||
export const getModelSelectionState = (providerName: ProviderName, modelName: string, modelSelectionOptions: ModelSelectionOptions | undefined): { isReasoningEnabled: boolean, reasoningBudget: number | undefined } => {
|
||||
const { canToggleReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).supportsReasoning || {}
|
||||
|
||||
const defaultEnabledVal = canToggleReasoning ? true : false
|
||||
const isReasoningEnabled = optionsOfModelSelection[providerName]?.[modelName]?.reasoningEnabled ?? defaultEnabledVal
|
||||
const reasoningBudget = optionsOfModelSelection[providerName]?.[modelName]?.reasoningBudget
|
||||
const isReasoningEnabled = modelSelectionOptions?.reasoningEnabled ?? defaultEnabledVal
|
||||
const reasoningBudget = reasoningBudgetSlider?.type === 'slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined
|
||||
return { isReasoningEnabled, reasoningBudget }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
import { ILLMMessageService } from './llmMessageService.js';
|
||||
import { ILLMMessageService } from './sendLLMMessageService.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { RefreshableProviderName, refreshableProviderNames, SettingsOfProvider } from './voidSettingsTypes.js';
|
||||
import { OllamaModelResponse, VLLMModelResponse } from './llmMessageTypes.js';
|
||||
import { OllamaModelResponse, VLLMModelResponse } from './sendLLMMessageTypes.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, VLLMModelResponse, } from './llmMessageTypes.js';
|
||||
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, VLLMModelResponse, } from './sendLLMMessageTypes.js';
|
||||
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
|
|
@ -13,7 +13,6 @@ import { generateUuid } from '../../../../base/common/uuid.js';
|
|||
import { Event } from '../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
import { displayInfoOfProviderName, isFeatureNameDisabled } from './voidSettingsTypes.js';
|
||||
// import { INotificationService } from '../../notification/common/notification.js';
|
||||
|
||||
// calls channel to implement features
|
||||
|
|
@ -67,7 +66,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
|
||||
// const service = ProxyChannel.toService<LLMMessageChannel>(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service
|
||||
// see llmMessageChannel.ts
|
||||
this.channel = this.mainProcessService.getChannel('void-channel-llmMessageService')
|
||||
this.channel = this.mainProcessService.getChannel('void-channel-llmMessage')
|
||||
|
||||
// .listen sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead
|
||||
// llm
|
||||
|
|
@ -83,32 +82,15 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
}
|
||||
|
||||
sendLLMMessage(params: ServiceSendLLMMessageParams) {
|
||||
const { onText, onFinalMessage, onError, ...proxyParams } = params;
|
||||
const { useProviderFor: featureName } = proxyParams
|
||||
const { onText, onFinalMessage, onError, modelSelection, ...proxyParams } = params;
|
||||
|
||||
// throw an error if no model/provider selected (this should usually never be reached, the UI should check this first, but might happen in cases like Apply where we haven't built much UI/checks yet, good practice to have check logic on backend)
|
||||
const isDisabled = isFeatureNameDisabled(featureName, this.voidSettingsService.state)
|
||||
const modelSelection = this.voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
if (isDisabled || modelSelection === null) {
|
||||
let message: string
|
||||
|
||||
if (isDisabled === 'addProvider' || isDisabled === 'providerNotAutoDetected')
|
||||
message = `Please add a provider in Void's Settings.`
|
||||
else if (isDisabled === 'addModel')
|
||||
message = `Please add a model.`
|
||||
else if (isDisabled === 'needToEnableModel')
|
||||
message = `Please enable a model.`
|
||||
else if (isDisabled === 'notFilledIn')
|
||||
message = `Please fill in Void's Settings${modelSelection !== null ? ` for ${displayInfoOfProviderName(modelSelection.providerName).title}` : ''}.`
|
||||
else
|
||||
message = `Please add a provider in Void's Settings.`
|
||||
|
||||
if (modelSelection === null) {
|
||||
const message = `Please add a provider in Void's Settings.`
|
||||
onError({ message, fullError: null })
|
||||
return null
|
||||
}
|
||||
|
||||
const { providerName, modelName } = modelSelection
|
||||
|
||||
// add state for request id
|
||||
const requestId = generateUuid();
|
||||
this.llmMessageHooks.onText[requestId] = onText
|
||||
|
|
@ -116,17 +98,15 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
|
|||
this.llmMessageHooks.onError[requestId] = onError
|
||||
|
||||
const { aiInstructions } = this.voidSettingsService.state.globalSettings
|
||||
const { settingsOfProvider, optionsOfModelSelection, } = this.voidSettingsService.state
|
||||
const { settingsOfProvider, } = this.voidSettingsService.state
|
||||
|
||||
// params will be stripped of all its functions over the IPC channel
|
||||
this.channel.call('sendLLMMessage', {
|
||||
...proxyParams,
|
||||
aiInstructions,
|
||||
requestId,
|
||||
providerName,
|
||||
modelName,
|
||||
settingsOfProvider,
|
||||
optionsOfModelSelection,
|
||||
modelSelection,
|
||||
} satisfies MainSendLLMMessageParams);
|
||||
|
||||
return requestId
|
||||
|
|
@ -3,9 +3,8 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { ChatMessage } from '../browser/chatThreadService.js'
|
||||
import type { InternalToolInfo, ToolName } from '../browser/toolsService.js'
|
||||
import { FeatureName, OptionsOfModelSelection, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js'
|
||||
import { ModelSelection, ModelSelectionOptions, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js'
|
||||
|
||||
|
||||
export const errorDetails = (fullError: Error | null): string | null => {
|
||||
|
|
@ -29,12 +28,15 @@ export const getErrorMessage: (error: unknown) => string = (error) => {
|
|||
|
||||
|
||||
export type LLMChatMessage = {
|
||||
role: 'system' | 'user';
|
||||
role: 'system';
|
||||
content: string;
|
||||
} | {
|
||||
role: 'user';
|
||||
content: string;
|
||||
} | {
|
||||
role: 'assistant',
|
||||
content: string; // text content
|
||||
rawAnthropicAssistantContent?: RawAnthropicAssistantContent[]; // used for anthropic signing
|
||||
anthropicReasoning: AnthropicReasoning[] | null;
|
||||
} | {
|
||||
role: 'tool';
|
||||
content: string; // result
|
||||
|
|
@ -50,31 +52,14 @@ export type ToolCallType = {
|
|||
id: string;
|
||||
}
|
||||
|
||||
export type RawAnthropicAssistantContent = { type: 'thinking'; thinking: string; signature: string; } | { type: 'redacted_thinking'; data: string } | { type: 'text', text: string }
|
||||
|
||||
export type AnthropicReasoning = ({ type: 'thinking'; thinking: any; signature: string; } | { type: 'redacted_thinking', data: any })
|
||||
|
||||
export type OnText = (p: { fullText: string; fullReasoning: string }) => void
|
||||
export type OnFinalMessage = (p: { fullText: string, toolCalls?: ToolCallType[], fullReasoning?: string, rawAnthropicAssistantContent?: RawAnthropicAssistantContent[] }) => void // id is tool_use_id
|
||||
export type OnError = (p: { message: string, fullError: Error | null }) => void
|
||||
export type OnFinalMessage = (p: { fullText: string; fullReasoning: string; toolCalls?: ToolCallType[]; anthropicReasoning: AnthropicReasoning[] | null }) => void // id is tool_use_id
|
||||
export type OnError = (p: { message: string; fullError: Error | null }) => void
|
||||
export type AbortRef = { current: (() => void) | null }
|
||||
|
||||
|
||||
export const toLLMChatMessage = (c: ChatMessage): LLMChatMessage | null => {
|
||||
if (c.role === 'user') {
|
||||
return { role: c.role, content: c.content || '(empty message)' }
|
||||
}
|
||||
else if (c.role === 'assistant')
|
||||
return { role: c.role, content: c.content || '(empty message)' }
|
||||
else if (c.role === 'tool')
|
||||
return { role: c.role, id: c.id, name: c.name, params: c.paramsStr, content: c.content || '(empty output)' }
|
||||
else if (c.role === 'tool_request')
|
||||
return null
|
||||
else {
|
||||
throw new Error(`Role ${(c as any).role} not recognized.`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type LLMFIMMessage = {
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
|
|
@ -97,8 +82,9 @@ export type ServiceSendLLMMessageParams = {
|
|||
onFinalMessage: OnFinalMessage;
|
||||
onError: OnError;
|
||||
logging: { loggingName: string, };
|
||||
useProviderFor: FeatureName;
|
||||
} & SendLLMType
|
||||
modelSelection: ModelSelection | null;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
} & SendLLMType;
|
||||
|
||||
// params to the true sendLLMMessage function
|
||||
export type SendLLMMessageParams = {
|
||||
|
|
@ -110,10 +96,10 @@ export type SendLLMMessageParams = {
|
|||
|
||||
aiInstructions: string;
|
||||
|
||||
providerName: ProviderName;
|
||||
modelName: string;
|
||||
modelSelection: ModelSelection;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
|
||||
settingsOfProvider: SettingsOfProvider;
|
||||
optionsOfModelSelection: OptionsOfModelSelection;
|
||||
} & SendLLMType
|
||||
|
||||
|
||||
|
|
@ -14,8 +14,10 @@ import { IMetricsService } from './metricsService.js';
|
|||
import { getModelCapabilities } from './modelCapabilities.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, defaultProviderSettings, ModelSelectionOptions, OptionsOfModelSelection } from './voidSettingsTypes.js';
|
||||
|
||||
// past values:
|
||||
// 'void.settingsServiceStorage'
|
||||
|
||||
const STORAGE_KEY = 'void.settingsServiceStorage'
|
||||
const STORAGE_KEY = 'void.settingsServiceStorageI'
|
||||
|
||||
|
||||
// name is the name in the dropdown
|
||||
|
|
@ -97,7 +99,7 @@ const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], opt
|
|||
|
||||
export const modelFilterOfFeatureName: { [featureName in FeatureName]: { filter: (o: ModelSelection) => boolean; emptyMessage: string | null } } = {
|
||||
'Autocomplete': { filter: o => getModelCapabilities(o.providerName, o.modelName).supportsFIM, emptyMessage: 'No models support FIM' },
|
||||
'Ctrl+L': { filter: o => true, emptyMessage: null },
|
||||
'Chat': { filter: o => true, emptyMessage: null },
|
||||
'Ctrl+K': { filter: o => true, emptyMessage: null },
|
||||
'Apply': { filter: o => true, emptyMessage: null },
|
||||
}
|
||||
|
|
@ -172,7 +174,7 @@ const _validatedState = (state: Omit<VoidSettingsState, '_modelOptions'>) => {
|
|||
const defaultState = () => {
|
||||
const d: VoidSettingsState = {
|
||||
settingsOfProvider: deepClone(defaultSettingsOfProvider),
|
||||
modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null, 'Apply': null },
|
||||
modelSelectionOfFeature: { 'Chat': null, 'Ctrl+K': null, 'Autocomplete': null, 'Apply': null },
|
||||
globalSettings: deepClone(defaultGlobalSettings),
|
||||
optionsOfModelSelection: {},
|
||||
_modelOptions: [], // computed later
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ export const modelSelectionsEqual = (m1: ModelSelection, m2: ModelSelection) =>
|
|||
}
|
||||
|
||||
// this is a state
|
||||
export const featureNames = ['Ctrl+L', 'Ctrl+K', 'Autocomplete', 'Apply'] as const
|
||||
export const featureNames = ['Chat', 'Ctrl+K', 'Autocomplete', 'Apply'] as const
|
||||
export type ModelSelectionOfFeature = Record<(typeof featureNames)[number], ModelSelection | null>
|
||||
export type FeatureName = keyof ModelSelectionOfFeature
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ export const displayInfoOfFeatureName = (featureName: FeatureName) => {
|
|||
else if (featureName === 'Ctrl+K')
|
||||
return 'Quick Edit'
|
||||
// sidebar:
|
||||
else if (featureName === 'Ctrl+L')
|
||||
else if (featureName === 'Chat')
|
||||
return 'Chat'
|
||||
else if (featureName === 'Apply')
|
||||
return 'Fast Apply'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RawAnthropicAssistantContent, LLMChatMessage, LLMFIMMessage } from '../../common/llmMessageTypes.js';
|
||||
import { AnthropicReasoning, LLMChatMessage, LLMFIMMessage } from '../../common/sendLLMMessageTypes.js';
|
||||
import { deepClone } from '../../../../../base/common/objects.js';
|
||||
|
||||
|
||||
|
|
@ -22,8 +22,7 @@ type InternalLLMChatMessage = {
|
|||
content: string;
|
||||
} | {
|
||||
role: 'assistant',
|
||||
content: string | (RawAnthropicAssistantContent | { type: 'text'; text: string })[];
|
||||
rawAnthropicAssistantContent?: RawAnthropicAssistantContent[] | undefined;
|
||||
content: string | (AnthropicReasoning | { type: 'text'; text: string })[];
|
||||
} | {
|
||||
role: 'tool';
|
||||
content: string; // result
|
||||
|
|
@ -33,7 +32,10 @@ type InternalLLMChatMessage = {
|
|||
}
|
||||
|
||||
|
||||
const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatMessage[] }) => {
|
||||
const EMPTY_MESSAGE = '(empty message)'
|
||||
const EMPTY_TOOL_CONTENT = '(empty content)'
|
||||
|
||||
const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatMessage[] }): { messages: LLMChatMessage[] } => {
|
||||
const messages = deepClone(messages_)
|
||||
const newMessages: LLMChatMessage[] = []
|
||||
if (messages.length >= 0) newMessages.push(messages[0])
|
||||
|
|
@ -41,12 +43,12 @@ const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatM
|
|||
// remove duplicate roles
|
||||
for (let i = 1; i < messages.length; i += 1) {
|
||||
const curr = messages[i]
|
||||
const prev = messages[i - 1]
|
||||
// if found a repeated role, put the current content in the prev
|
||||
if ((curr.role === 'user' && prev.role === 'user') || (curr.role === 'assistant' && prev.role === 'assistant')) {
|
||||
prev.content += '\n' + curr.content
|
||||
continue
|
||||
}
|
||||
// const prev = messages[i - 1]
|
||||
// // if found a repeated role, put the current content in the prev
|
||||
// if ((curr.role === 'assistant' && prev.role === 'assistant')) {
|
||||
// prev.content += '\n' + curr.content
|
||||
// continue
|
||||
// }
|
||||
// add the message
|
||||
newMessages.push(curr)
|
||||
}
|
||||
|
|
@ -58,29 +60,6 @@ const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatM
|
|||
|
||||
|
||||
|
||||
// remove rawAnthropicAssistantContent, and make content equal to it if supportsAnthropicContent
|
||||
const prepareMessages_anthropicContent = ({ messages, supportsAnthropicContent }: { messages: LLMChatMessage[], supportsAnthropicContent: boolean }) => {
|
||||
const newMessages: InternalLLMChatMessage[] = []
|
||||
for (const m of messages) {
|
||||
if (m.role !== 'assistant') {
|
||||
newMessages.push(m)
|
||||
continue
|
||||
}
|
||||
let newMessage: InternalLLMChatMessage
|
||||
if (supportsAnthropicContent) {
|
||||
const newContent = m.rawAnthropicAssistantContent
|
||||
newMessage = { role: 'assistant', content: newContent ?? m.content }
|
||||
}
|
||||
else {
|
||||
newMessage = m
|
||||
}
|
||||
delete newMessage.rawAnthropicAssistantContent // important to delete this field
|
||||
newMessages.push(m)
|
||||
}
|
||||
return { messages: newMessages }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// no matter whether the model supports a system message or not (or what format it supports), add it in some way
|
||||
|
|
@ -173,27 +152,27 @@ openai on prompting - https://platform.openai.com/docs/guides/reasoning#advice-o
|
|||
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: (
|
||||
Exclude<InternalLLMChatMessage, { role: 'assistant' | 'tool' }> | {
|
||||
role: 'assistant',
|
||||
content: string | object[];
|
||||
tool_calls?: {
|
||||
type: 'function';
|
||||
id: string;
|
||||
function: {
|
||||
name: string;
|
||||
arguments: string;
|
||||
}
|
||||
}[]
|
||||
} | {
|
||||
role: 'tool',
|
||||
id: string; // old val
|
||||
tool_call_id: string; // new val
|
||||
content: string;
|
||||
}
|
||||
)[] = [];
|
||||
const newMessages: PrepareMessagesToolsOpenAI = [];
|
||||
|
||||
for (let i = 0; i < messages.length; i += 1) {
|
||||
const currMsg = messages[i]
|
||||
|
|
@ -219,9 +198,8 @@ const prepareMessages_tools_openai = ({ messages }: { messages: InternalLLMChatM
|
|||
// add the tool
|
||||
newMessages.push({
|
||||
role: 'tool',
|
||||
id: currMsg.id,
|
||||
content: currMsg.content,
|
||||
tool_call_id: currMsg.id,
|
||||
content: currMsg.content || EMPTY_TOOL_CONTENT,
|
||||
})
|
||||
}
|
||||
return { messages: newMessages }
|
||||
|
|
@ -250,34 +228,44 @@ anthropic RESPONSE (role=user):
|
|||
}]
|
||||
*/
|
||||
|
||||
const prepareMessages_tools_anthropic = ({ messages }: { messages: InternalLLMChatMessage[], }) => {
|
||||
const newMessages: (
|
||||
Exclude<InternalLLMChatMessage, { role: 'assistant' | 'user' }> | {
|
||||
role: 'assistant',
|
||||
content: string | (
|
||||
| RawAnthropicAssistantContent
|
||||
| {
|
||||
type: 'text';
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: 'tool_use';
|
||||
name: string;
|
||||
input: Record<string, any>;
|
||||
id: string;
|
||||
})[]
|
||||
} | {
|
||||
role: 'user',
|
||||
content: string | ({
|
||||
type PrepareMessagesToolsAnthropic = (
|
||||
Exclude<InternalLLMChatMessage, { role: 'assistant' | 'user' }> | {
|
||||
role: 'assistant',
|
||||
content: string | (
|
||||
| AnthropicReasoning
|
||||
| {
|
||||
type: 'text';
|
||||
text: string;
|
||||
} | {
|
||||
type: 'tool_result';
|
||||
tool_use_id: string;
|
||||
content: string;
|
||||
}
|
||||
| {
|
||||
type: 'tool_use';
|
||||
name: string;
|
||||
input: Record<string, any>;
|
||||
id: string;
|
||||
})[]
|
||||
}
|
||||
)[] = messages;
|
||||
} | {
|
||||
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) {
|
||||
|
|
@ -296,7 +284,7 @@ const prepareMessages_tools_anthropic = ({ messages }: { messages: InternalLLMCh
|
|||
newMessages[i] = {
|
||||
role: 'user',
|
||||
content: [
|
||||
...[{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content }] as const,
|
||||
...[{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content || EMPTY_TOOL_CONTENT }] as const,
|
||||
...currMsg.content ? [{ type: 'text', text: currMsg.content }] as const : [],
|
||||
]
|
||||
}
|
||||
|
|
@ -307,8 +295,9 @@ const prepareMessages_tools_anthropic = ({ messages }: { messages: InternalLLMCh
|
|||
|
||||
|
||||
|
||||
type PrepareMessagesTools = PrepareMessagesToolsAnthropic | PrepareMessagesToolsOpenAI
|
||||
|
||||
const prepareMessages_tools = ({ messages, supportsTools }: { messages: InternalLLMChatMessage[], supportsTools: false | 'anthropic-style' | 'openai-style' }) => {
|
||||
const prepareMessages_tools = ({ messages, supportsTools }: { messages: InternalLLMChatMessage[], supportsTools: false | 'anthropic-style' | 'openai-style' }): { messages: PrepareMessagesTools } => {
|
||||
if (!supportsTools) {
|
||||
return { messages: messages }
|
||||
}
|
||||
|
|
@ -324,6 +313,128 @@ const prepareMessages_tools = ({ messages, supportsTools }: { messages: Internal
|
|||
}
|
||||
|
||||
|
||||
// remove rawAnthropicAssistantContent, and make content equal to it if supportsAnthropicContent
|
||||
const prepareMessages_anthropicContent = ({ 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: PrepareMessagesTools }): { messages: PrepareMessagesTools } => {
|
||||
for (const currMsg of messages) {
|
||||
|
||||
// don't do this for tools
|
||||
if (currMsg.role === 'tool') continue
|
||||
|
||||
// don't do this for assistant or user messages that have tool_calls or tool_results
|
||||
const oai = currMsg as PrepareMessagesToolsOpenAI[0]
|
||||
if (oai.role === 'assistant') {
|
||||
if (oai.tool_calls) continue
|
||||
}
|
||||
const anth = currMsg as PrepareMessagesToolsAnthropic[0]
|
||||
if (anth.role === 'assistant' || anth.role === 'user') {
|
||||
if (typeof anth.content !== 'string') {
|
||||
const hasContent = anth.content.find(c => c.type === 'tool_use' || c.type === 'tool_result')
|
||||
if (hasContent) continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (typeof currMsg.content === 'string') {
|
||||
currMsg.content = currMsg.content || EMPTY_MESSAGE
|
||||
}
|
||||
else {
|
||||
for (const c of currMsg.content) {
|
||||
if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE
|
||||
else if (c.type === 'tool_use') { }
|
||||
else if (c.type === 'tool_result') { }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return { messages }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- CHAT ---
|
||||
|
||||
export const prepareMessages = ({
|
||||
messages,
|
||||
aiInstructions,
|
||||
supportsSystemMessage,
|
||||
supportsTools,
|
||||
supportsAnthropicReasoningSignature,
|
||||
}: {
|
||||
messages: LLMChatMessage[],
|
||||
aiInstructions: string,
|
||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
|
||||
supportsTools: false | 'anthropic-style' | 'openai-style',
|
||||
supportsAnthropicReasoningSignature: boolean,
|
||||
}) => {
|
||||
const { messages: messages1 } = prepareMessages_normalize({ messages })
|
||||
const { messages: messages2 } = prepareMessages_anthropicContent({ messages: messages1, supportsAnthropicReasoningSignature })
|
||||
const { messages: messages3, separateSystemMessageStr } = prepareMessages_systemMessage({ messages: messages2, aiInstructions, supportsSystemMessage })
|
||||
const { messages: messages4 } = prepareMessages_tools({ messages: messages3, supportsTools })
|
||||
const { messages: messages5 } = prepareMessages_noEmptyMessage({ messages: messages4 })
|
||||
return {
|
||||
messages: messages5 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -357,60 +468,3 @@ gemini response:
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
// --- CHAT ---
|
||||
|
||||
export const prepareMessages = ({
|
||||
messages,
|
||||
aiInstructions,
|
||||
supportsSystemMessage,
|
||||
supportsTools,
|
||||
supportsAnthropicContent,
|
||||
}: {
|
||||
messages: LLMChatMessage[],
|
||||
aiInstructions: string,
|
||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
|
||||
supportsTools: false | 'anthropic-style' | 'openai-style',
|
||||
supportsAnthropicContent: boolean,
|
||||
}) => {
|
||||
const { messages: messages1 } = prepareMessages_normalize({ messages })
|
||||
const { messages: messages2 } = prepareMessages_anthropicContent({ messages: messages1, supportsAnthropicContent })
|
||||
const { messages: messages3, separateSystemMessageStr } = prepareMessages_systemMessage({ messages: messages2, aiInstructions, supportsSystemMessage })
|
||||
const { messages: messages4 } = prepareMessages_tools({ messages: messages3, supportsTools })
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import OpenAI, { ClientOptions } from 'openai';
|
|||
|
||||
import { Model as OpenAIModel } from 'openai/resources/models.js';
|
||||
import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper } from '../../browser/helpers/extractCodeFromResult.js';
|
||||
import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/llmMessageTypes.js';
|
||||
import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js';
|
||||
import { InternalToolInfo, isAToolName, ToolName } from '../../browser/toolsService.js';
|
||||
import { defaultProviderSettings, displayInfoOfProviderName, OptionsOfModelSelection, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js';
|
||||
import { getModelSelectionState, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js';
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ type InternalCommonMessageParams = {
|
|||
onError: OnError;
|
||||
providerName: ProviderName;
|
||||
settingsOfProvider: SettingsOfProvider;
|
||||
optionsOfModelSelection: OptionsOfModelSelection;
|
||||
modelSelectionOptions: ModelSelectionOptions | undefined;
|
||||
modelName: string;
|
||||
_setAborter: (aborter: () => void) => void;
|
||||
}
|
||||
|
|
@ -140,7 +140,7 @@ const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError
|
|||
})
|
||||
.then(async response => {
|
||||
const fullText = response.choices[0]?.text
|
||||
onFinalMessage({ fullText, });
|
||||
onFinalMessage({ fullText, fullReasoning: '', anthropicReasoning: null });
|
||||
})
|
||||
.catch(error => {
|
||||
if (error instanceof OpenAI.APIError && error.status === 401) { onError({ message: invalidApiKeyMessage(providerName), fullError: error }); }
|
||||
|
|
@ -168,7 +168,7 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
|||
|
||||
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
||||
|
||||
const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicContent: false }) // can change supportsAnthropicContent if e.g. OpenRouter starts supporting anthropic extended thinking
|
||||
const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: false })
|
||||
const tools = (supportsTools && ((tools_?.length ?? 0) !== 0)) ? tools_?.map(tool => toOpenAICompatibleTool(tool)) : undefined
|
||||
|
||||
const includeInPayload = canIOReasoning ? providerReasoningIOSettings?.input?.includeInPayload || {} : {}
|
||||
|
|
@ -222,9 +222,9 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
|||
else {
|
||||
if (manuallyParseReasoning) {
|
||||
const { fullText, fullReasoning } = extractReasoningOnFinalMessage(fullTextSoFar, openSourceThinkTags)
|
||||
onFinalMessage({ fullText, fullReasoning, toolCalls });
|
||||
onFinalMessage({ fullText, fullReasoning, toolCalls, anthropicReasoning: null });
|
||||
} else {
|
||||
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, toolCalls });
|
||||
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, toolCalls, anthropicReasoning: null });
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -280,7 +280,7 @@ const toAnthropicTool = (toolInfo: InternalToolInfo) => {
|
|||
} satisfies Anthropic.Messages.Tool
|
||||
}
|
||||
|
||||
const toolCallsFrom_AnthropicContent = (content: Anthropic.Messages.ContentBlock[]): ToolCallsFrom_ReturnType => {
|
||||
const toolCallsFrom_Anthropic = (content: Anthropic.Messages.ContentBlock[]): ToolCallsFrom_ReturnType => {
|
||||
return content.map(c => {
|
||||
if (c.type !== 'tool_use') return null
|
||||
if (!isAToolName(c.name)) return null
|
||||
|
|
@ -288,7 +288,7 @@ const toolCallsFrom_AnthropicContent = (content: Anthropic.Messages.ContentBlock
|
|||
}).filter(t => !!t)
|
||||
}
|
||||
|
||||
const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalMessage, onError, settingsOfProvider, optionsOfModelSelection, modelName: modelName_, _setAborter, aiInstructions, tools: tools_ }: SendChatParams_Internal) => {
|
||||
const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, aiInstructions, tools: tools_ }: SendChatParams_Internal) => {
|
||||
const {
|
||||
modelName,
|
||||
supportsSystemMessage,
|
||||
|
|
@ -299,9 +299,9 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
const {
|
||||
isReasoningEnabled,
|
||||
reasoningBudget,
|
||||
} = getModelSelectionState(providerName, modelName_, optionsOfModelSelection) // user's modelName_ here
|
||||
} = getModelSelectionState(providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
||||
|
||||
const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicContent: true })
|
||||
const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: true })
|
||||
|
||||
const thisConfig = settingsOfProvider.anthropic
|
||||
const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true });
|
||||
|
|
@ -370,8 +370,9 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
|
||||
// on done - (or when error/fail) - this is called AFTER last streamEvent
|
||||
stream.on('finalMessage', (response) => {
|
||||
const toolCalls = toolCallsFrom_AnthropicContent(response.content)
|
||||
onFinalMessage({ fullText, fullReasoning, toolCalls, rawAnthropicAssistantContent: response.content as any })
|
||||
const toolCalls = toolCallsFrom_Anthropic(response.content)
|
||||
const anthropicReasoning = response.content.filter(c => c.type === 'thinking' || c.type === 'redacted_thinking')
|
||||
onFinalMessage({ fullText, fullReasoning, toolCalls, anthropicReasoning })
|
||||
})
|
||||
// on error
|
||||
stream.on('error', (error) => {
|
||||
|
|
@ -455,7 +456,7 @@ const sendOllamaFIM = ({ messages: messages_, onFinalMessage, onError, settingsO
|
|||
const newText = chunk.response
|
||||
fullText += newText
|
||||
}
|
||||
onFinalMessage({ fullText })
|
||||
onFinalMessage({ fullText, fullReasoning: '', anthropicReasoning: null })
|
||||
})
|
||||
// when error/fail
|
||||
.catch((error) => {
|
||||
|
|
@ -494,6 +495,11 @@ export const sendLLMMessageToProviderImplementation = {
|
|||
sendFIM: null,
|
||||
list: null,
|
||||
},
|
||||
// mistral: {
|
||||
// sendChat: , // TODO
|
||||
// sendFIM: , // TODO // https://docs.mistral.ai/api/#tag/fim
|
||||
// list: null,
|
||||
// },
|
||||
ollama: {
|
||||
sendChat: (params) => _sendOpenAICompatibleChat(params),
|
||||
sendFIM: sendOllamaFIM,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SendLLMMessageParams, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes.js';
|
||||
import { SendLLMMessageParams, OnText, OnFinalMessage, OnError } from '../../common/sendLLMMessageTypes.js';
|
||||
import { IMetricsService } from '../../common/metricsService.js';
|
||||
import { displayInfoOfProviderName } from '../../common/voidSettingsTypes.js';
|
||||
import { sendLLMMessageToProviderImplementation } from './sendLLMMessage.impl.js';
|
||||
|
|
@ -19,9 +19,8 @@ export const sendLLMMessage = ({
|
|||
abortRef: abortRef_,
|
||||
logging: { loggingName },
|
||||
settingsOfProvider,
|
||||
optionsOfModelSelection,
|
||||
providerName,
|
||||
modelName,
|
||||
modelSelection,
|
||||
modelSelectionOptions,
|
||||
tools,
|
||||
}: SendLLMMessageParams,
|
||||
|
||||
|
|
@ -29,6 +28,8 @@ export const sendLLMMessage = ({
|
|||
) => {
|
||||
|
||||
|
||||
const { providerName, modelName } = modelSelection
|
||||
|
||||
// only captures number of messages and message "shape", no actual code, instructions, prompts, etc
|
||||
const captureLLMEvent = (eventId: string, extras?: object) => {
|
||||
metricsService.capture(eventId, {
|
||||
|
|
@ -105,18 +106,19 @@ export const sendLLMMessage = ({
|
|||
}
|
||||
const { sendFIM, sendChat } = implementation
|
||||
if (messagesType === 'chatMessages') {
|
||||
sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, optionsOfModelSelection, modelName, _setAborter, providerName, aiInstructions, tools })
|
||||
sendChat({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, aiInstructions, tools })
|
||||
return
|
||||
}
|
||||
if (messagesType === 'FIMMessage') {
|
||||
if (sendFIM) {
|
||||
sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, optionsOfModelSelection, modelName, _setAborter, providerName, aiInstructions })
|
||||
sendFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName, _setAborter, providerName, aiInstructions })
|
||||
return
|
||||
}
|
||||
onError({ message: `Error: This provider does not support Autocomplete yet.`, fullError: null })
|
||||
return
|
||||
}
|
||||
onError({ message: `Error: Message type "${messagesType}" not recognized.`, fullError: null })
|
||||
return
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { IServerChannel } from '../../../../base/parts/ipc/common/ipc.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainSendLLMMessageParams, AbortRef, SendLLMMessageParams, MainLLMMessageAbortParams, ModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, OllamaModelResponse, VLLMModelResponse, MainModelListParams, } from '../common/llmMessageTypes.js';
|
||||
import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, MainSendLLMMessageParams, AbortRef, SendLLMMessageParams, MainLLMMessageAbortParams, ModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, OllamaModelResponse, VLLMModelResponse, MainModelListParams, } from '../common/sendLLMMessageTypes.js';
|
||||
import { sendLLMMessage } from './llmMessage/sendLLMMessage.js'
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { sendLLMMessageToProviderImplementation } from './llmMessage/sendLLMMessage.impl.js';
|
||||
Loading…
Reference in a new issue