mirror of
https://github.com/voideditor/void
synced 2026-05-24 01:48:25 +00:00
native gemini tools, editing UI, tool prompts
This commit is contained in:
parent
cec0df989c
commit
f166fa3581
13 changed files with 528 additions and 286 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -13,7 +13,7 @@
|
|||
"@anthropic-ai/sdk": "^0.40.0",
|
||||
"@c4312/eventsource-umd": "^3.0.5",
|
||||
"@floating-ui/react": "^0.27.8",
|
||||
"@google/generative-ai": "^0.24.0",
|
||||
"@google/generative-ai": "^0.24.1",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@mistralai/mistralai": "^1.6.0",
|
||||
|
|
@ -1817,9 +1817,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@google/generative-ai": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.0.tgz",
|
||||
"integrity": "sha512-fnEITCGEB7NdX0BhoYZ/cq/7WPZ1QS5IzJJfC3Tg/OwkvBetMiVJciyaan297OvE4B9Jg1xvo0zIazX/9sGu1Q==",
|
||||
"version": "0.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz",
|
||||
"integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
"@anthropic-ai/sdk": "^0.40.0",
|
||||
"@c4312/eventsource-umd": "^3.0.5",
|
||||
"@floating-ui/react": "^0.27.8",
|
||||
"@google/generative-ai": "^0.24.0",
|
||||
"@google/generative-ai": "^0.24.1",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@mistralai/mistralai": "^1.6.0",
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
|
|||
import { ChatMessage } from '../common/chatThreadServiceTypes.js';
|
||||
import { getIsReasoningEnabledState, getMaxOutputTokens, getModelCapabilities } from '../common/modelCapabilities.js';
|
||||
import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
|
||||
import { AnthropicLLMChatMessage, AnthropicReasoning, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { AnthropicLLMChatMessage, AnthropicReasoning, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { ChatMode, FeatureName, ModelSelection } from '../common/voidSettingsTypes.js';
|
||||
import { ChatMode, FeatureName, ModelSelection, ProviderName } from '../common/voidSettingsTypes.js';
|
||||
import { IDirectoryStrService } from './directoryStrService.js';
|
||||
import { ITerminalToolService } from './terminalToolService.js';
|
||||
import { IVoidModelService } from '../common/voidModelService.js';
|
||||
|
|
@ -36,8 +36,6 @@ type SimpleLLMMessage = {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const EMPTY_MESSAGE = '(empty message)'
|
||||
|
||||
const CHARS_PER_TOKEN = 4
|
||||
|
|
@ -69,7 +67,7 @@ openai on developer system message - https://cdn.openai.com/spec/model-spec-2024
|
|||
*/
|
||||
|
||||
|
||||
const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): LLMChatMessage[] => {
|
||||
const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): AnthropicOrOpenAILLMMessage[] => {
|
||||
|
||||
const newMessages: OpenAILLMChatMessage[] = [];
|
||||
|
||||
|
|
@ -136,8 +134,9 @@ assistant: ...content, call(name, id, params)
|
|||
user: ...content, result(id, content)
|
||||
*/
|
||||
|
||||
type AnthropicOrOpenAILLMMessage = AnthropicLLMChatMessage | OpenAILLMChatMessage
|
||||
|
||||
const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): LLMChatMessage[] => {
|
||||
const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): AnthropicOrOpenAILLMMessage[] => {
|
||||
const newMessages: (AnthropicLLMChatMessage | (SimpleLLMMessage & { role: 'tool' }))[] = messages;
|
||||
|
||||
for (let i = 0; i < messages.length; i += 1) {
|
||||
|
|
@ -195,9 +194,9 @@ const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsA
|
|||
}
|
||||
|
||||
|
||||
const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): LLMChatMessage[] => {
|
||||
const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): AnthropicOrOpenAILLMMessage[] => {
|
||||
|
||||
const llmChatMessages: LLMChatMessage[] = [];
|
||||
const llmChatMessages: AnthropicOrOpenAILLMMessage[] = [];
|
||||
for (let i = 0; i < messages.length; i += 1) {
|
||||
|
||||
const c = messages[i]
|
||||
|
|
@ -206,7 +205,7 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop
|
|||
if (c.role === 'assistant') {
|
||||
// if called a tool (message after it), re-add its XML to the message
|
||||
// alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere
|
||||
let content: LLMChatMessage['content'] = c.content
|
||||
let content: AnthropicOrOpenAILLMMessage['content'] = c.content
|
||||
if (next?.role === 'tool') {
|
||||
content = `${content}\n\n${reParsedToolXMLString(next.name, next.rawParams)}`
|
||||
}
|
||||
|
|
@ -239,24 +238,20 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop
|
|||
|
||||
|
||||
|
||||
const prepareMessages_providerSpecific = (messages: SimpleLLMMessage[], specialToolFormat: 'openai-style' | 'anthropic-style' | undefined, supportsAnthropicReasoning: boolean): LLMChatMessage[] => {
|
||||
const llmChatMessages: LLMChatMessage[] = []
|
||||
if (!specialToolFormat) { // XML tool behavior
|
||||
return prepareMessages_XML_tools(messages, supportsAnthropicReasoning)
|
||||
}
|
||||
else if (specialToolFormat === 'anthropic-style') {
|
||||
return prepareMessages_anthropic_tools(messages, supportsAnthropicReasoning)
|
||||
}
|
||||
else if (specialToolFormat === 'openai-style') {
|
||||
return prepareMessages_openai_tools(messages)
|
||||
}
|
||||
return llmChatMessages
|
||||
}
|
||||
|
||||
export type GeminiMessage = {
|
||||
role: 'user' | 'model'; // Gemini uses 'user' and 'model' roles
|
||||
parts: (
|
||||
| { text: string; }
|
||||
| { functionCall: { tool_call: any } }
|
||||
| { functionResponse: { name: ToolName, response: { result: string } } }
|
||||
)[];
|
||||
};
|
||||
|
||||
|
||||
// --- CHAT ---
|
||||
|
||||
const prepareMessages = ({
|
||||
const prepareOpenAIOrAnthropicMessages = ({
|
||||
messages,
|
||||
systemMessage,
|
||||
aiInstructions,
|
||||
|
|
@ -274,7 +269,7 @@ const prepareMessages = ({
|
|||
supportsAnthropicReasoning: boolean,
|
||||
contextWindow: number,
|
||||
maxOutputTokens: number | null | undefined,
|
||||
}): { messages: LLMChatMessage[], separateSystemMessage: string | undefined } => {
|
||||
}): { messages: AnthropicOrOpenAILLMMessage[], separateSystemMessage: string | undefined } => {
|
||||
maxOutputTokens = maxOutputTokens ?? 4_096 // default to 4096
|
||||
|
||||
// ================ trim ================
|
||||
|
|
@ -350,7 +345,19 @@ const prepareMessages = ({
|
|||
}
|
||||
|
||||
// ================ tools and anthropicReasoning ================
|
||||
const llmMessages: LLMChatMessage[] = prepareMessages_providerSpecific(messages, specialToolFormat, supportsAnthropicReasoning)
|
||||
|
||||
let llmChatMessages: AnthropicOrOpenAILLMMessage[] = []
|
||||
if (!specialToolFormat) { // XML tool behavior
|
||||
llmChatMessages = prepareMessages_XML_tools(messages, supportsAnthropicReasoning)
|
||||
}
|
||||
else if (specialToolFormat === 'anthropic-style') {
|
||||
llmChatMessages = prepareMessages_anthropic_tools(messages, supportsAnthropicReasoning)
|
||||
}
|
||||
else if (specialToolFormat === 'openai-style') {
|
||||
llmChatMessages = prepareMessages_openai_tools(messages)
|
||||
}
|
||||
const llmMessages = llmChatMessages
|
||||
|
||||
|
||||
// ================ system message concat ================
|
||||
|
||||
|
|
@ -406,19 +413,92 @@ const prepareMessages = ({
|
|||
|
||||
|
||||
|
||||
type GeminiUserPart = (GeminiLLMChatMessage & { role: 'user' })['parts'][0]
|
||||
type GeminiModelPart = (GeminiLLMChatMessage & { role: 'model' })['parts'][0]
|
||||
const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => {
|
||||
let latestToolName: ToolName | undefined = undefined
|
||||
const messages2: GeminiLLMChatMessage[] = messages.map((m): GeminiLLMChatMessage | null => {
|
||||
if (m.role === 'assistant') {
|
||||
if (typeof m.content === 'string') {
|
||||
return { role: 'model', parts: [{ text: m.content }] }
|
||||
}
|
||||
else {
|
||||
const parts: GeminiModelPart[] = m.content.map((c): GeminiModelPart | null => {
|
||||
if (c.type === 'text') {
|
||||
return { text: c.text }
|
||||
}
|
||||
else if (c.type === 'tool_use') {
|
||||
latestToolName = c.name as ToolName
|
||||
return { functionCall: { name: c.name as ToolName, args: c.input } }
|
||||
}
|
||||
else return null
|
||||
}).filter(m => !!m)
|
||||
return { role: 'model', parts, }
|
||||
}
|
||||
}
|
||||
else if (m.role === 'user') {
|
||||
if (typeof m.content === 'string') {
|
||||
return { role: 'user', parts: [{ text: m.content }] } satisfies GeminiLLMChatMessage
|
||||
}
|
||||
else {
|
||||
const parts: GeminiUserPart[] = m.content.map((c): GeminiUserPart | null => {
|
||||
if (c.type === 'text') {
|
||||
return { text: c.text }
|
||||
}
|
||||
else if (c.type === 'tool_result') {
|
||||
if (!latestToolName) return null
|
||||
return { functionResponse: { name: latestToolName, response: { result: c.content } } }
|
||||
}
|
||||
else return null
|
||||
}).filter(m => !!m)
|
||||
return { role: 'user', parts, }
|
||||
}
|
||||
|
||||
}
|
||||
else return null
|
||||
}).filter(m => !!m)
|
||||
|
||||
return messages2
|
||||
}
|
||||
|
||||
|
||||
const prepareMessages = (params: {
|
||||
messages: SimpleLLMMessage[],
|
||||
systemMessage: string,
|
||||
aiInstructions: string,
|
||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
|
||||
specialToolFormat: 'openai-style' | 'anthropic-style' | 'gemini-style' | undefined,
|
||||
supportsAnthropicReasoning: boolean,
|
||||
contextWindow: number,
|
||||
maxOutputTokens: number | null | undefined,
|
||||
providerName: ProviderName
|
||||
}): { messages: LLMChatMessage[], separateSystemMessage: string | undefined } => {
|
||||
|
||||
const specialFormat = params.specialToolFormat // this is just for ts idiocy
|
||||
if (params.providerName === 'gemini') {
|
||||
// treat as anthropic style, then convert to gemini style
|
||||
const res = prepareOpenAIOrAnthropicMessages({ ...params, specialToolFormat: specialFormat === 'gemini-style' ? 'anthropic-style' : undefined })
|
||||
const messages = res.messages as AnthropicLLMChatMessage[]
|
||||
const messages2 = prepareGeminiMessages(messages)
|
||||
return { messages: messages2, separateSystemMessage: res.separateSystemMessage }
|
||||
}
|
||||
else {
|
||||
if (specialFormat === 'gemini-style') {
|
||||
throw new Error(`Tried preparing messages with tool format ${params.specialToolFormat} but the provider was ${params.providerName}, not Gemini.`)
|
||||
}
|
||||
}
|
||||
|
||||
return prepareOpenAIOrAnthropicMessages({ ...params, specialToolFormat: specialFormat })
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface IConvertToLLMMessageService {
|
||||
readonly _serviceBrand: undefined;
|
||||
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined }
|
||||
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }>
|
||||
prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined, geminiMessages?: GeminiMessage[] }
|
||||
prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined, geminiMessages?: GeminiMessage[] }>
|
||||
prepareFIMMessage(opts: { messages: LLMFIMMessage, }): { prefix: string, suffix: string, stopTokens: string[] }
|
||||
|
||||
}
|
||||
|
||||
export const IConvertToLLMMessageService = createDecorator<IConvertToLLMMessageService>('ConvertToLLMMessageService');
|
||||
|
|
@ -470,7 +550,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
|
||||
|
||||
// system message
|
||||
private _generateChatMessagesSystemMessage = async (chatMode: ChatMode, specialToolFormat: 'openai-style' | 'anthropic-style' | undefined) => {
|
||||
private _generateChatMessagesSystemMessage = async (chatMode: ChatMode, specialToolFormat: 'openai-style' | 'anthropic-style' | 'gemini-style' | undefined) => {
|
||||
const workspaceFolders = this.workspaceContextService.getWorkspace().folders.map(f => f.uri.fsPath)
|
||||
|
||||
const openedURIs = this.modelService.getModels().filter(m => m.isAttachedToEditor()).map(m => m.uri.fsPath) || [];
|
||||
|
|
@ -551,6 +631,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
supportsAnthropicReasoning: providerName === 'anthropic',
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
providerName,
|
||||
})
|
||||
return { messages, separateSystemMessage };
|
||||
}
|
||||
|
|
@ -582,6 +663,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
supportsAnthropicReasoning: providerName === 'anthropic',
|
||||
contextWindow,
|
||||
maxOutputTokens,
|
||||
providerName,
|
||||
})
|
||||
return { messages, separateSystemMessage };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import { ICodeEditorService } from '../../../../editor/browser/services/codeEdit
|
|||
import { findDiffs } from './helpers/findDiffs.js';
|
||||
import { EndOfLinePreference, IModelDecorationOptions, ITextModel } from '../../../../editor/common/model.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
import { registerColor } from '../../../../platform/theme/common/colorUtils.js';
|
||||
import { Color, RGBA } from '../../../../base/common/color.js';
|
||||
import { IModelService } from '../../../../editor/common/services/model.js';
|
||||
import { IUndoRedoElement, IUndoRedoService, UndoRedoElementType } from '../../../../platform/undoRedo/common/undoRedo.js';
|
||||
import { RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js';
|
||||
|
|
@ -48,27 +46,6 @@ import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
|||
// import { isMacintosh } from '../../../../base/common/platform.js';
|
||||
// import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
|
||||
const configOfBG = (color: Color) => {
|
||||
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
||||
}
|
||||
// gets converted to --vscode-void-greenBG, see void.css, asCssVariable
|
||||
const greenBG = new Color(new RGBA(155, 185, 85, .2)); // default is RGBA(155, 185, 85, .2)
|
||||
registerColor('void.greenBG', configOfBG(greenBG), '', true);
|
||||
|
||||
const redBG = new Color(new RGBA(255, 0, 0, .2)); // default is RGBA(255, 0, 0, .2)
|
||||
registerColor('void.redBG', configOfBG(redBG), '', true);
|
||||
|
||||
const sweepBG = new Color(new RGBA(100, 100, 100, .2));
|
||||
registerColor('void.sweepBG', configOfBG(sweepBG), '', true);
|
||||
|
||||
const highlightBG = new Color(new RGBA(100, 100, 100, .1));
|
||||
registerColor('void.highlightBG', configOfBG(highlightBG), '', true);
|
||||
|
||||
const sweepIdxBG = new Color(new RGBA(100, 100, 100, .5));
|
||||
registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true);
|
||||
|
||||
|
||||
|
||||
const numLinesOfStr = (str: string) => str.split('\n').length
|
||||
|
||||
|
||||
|
|
@ -129,10 +106,10 @@ const removeWhitespaceExceptNewlines = (str: string): string => {
|
|||
|
||||
// finds block.orig in fileContents and return its range in file
|
||||
// startingAtLine is 1-indexed and inclusive
|
||||
const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, startingAtLine?: number) => {
|
||||
const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, opts: { startingAtLine?: number, returnType: 'lines' | 'indices' }) => {
|
||||
|
||||
const startLineIdx = (fileContents: string) => startingAtLine !== undefined ?
|
||||
fileContents.split('\n').slice(0, startingAtLine).join('\n').length // num characters in all lines before startingAtLine
|
||||
const startLineIdx = (fileContents: string) => opts?.startingAtLine !== undefined ?
|
||||
fileContents.split('\n').slice(0, opts.startingAtLine).join('\n').length // num characters in all lines before startingAtLine
|
||||
: 0
|
||||
|
||||
// idx = starting index in fileContents
|
||||
|
|
@ -148,10 +125,18 @@ const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveW
|
|||
if (idx === -1) return 'Not found' as const
|
||||
const lastIdx = fileContents.lastIndexOf(text)
|
||||
if (lastIdx !== idx) return 'Not unique' as const
|
||||
const startLine = fileContents.substring(0, idx).split('\n').length
|
||||
const numLines = numLinesOfStr(text)
|
||||
const endLine = startLine + numLines - 1
|
||||
return [startLine, endLine] as const
|
||||
|
||||
if (opts.returnType === 'lines') {
|
||||
const startLine = fileContents.substring(0, idx).split('\n').length
|
||||
const numLines = numLinesOfStr(text)
|
||||
const endLine = startLine + numLines - 1
|
||||
return [startLine, endLine] as const
|
||||
}
|
||||
|
||||
else if (opts.returnType === 'indices') {
|
||||
return [idx, idx + text.length] as const
|
||||
}
|
||||
else throw new Error(`findTextInCode: Invalid returnType ${opts.returnType}`)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1573,14 +1558,14 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
private _errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => {
|
||||
|
||||
const problematicCode = `The problematic ORIGINAL code was:\n${tripleTick[0]}\n${JSON.stringify(blockOrig)}\n${tripleTick[1]}`
|
||||
const problematicCode = `${tripleTick[0]}\n${JSON.stringify(blockOrig)}\n${tripleTick[1]}`
|
||||
|
||||
const descStr = str === `Not found` ?
|
||||
`The most recent ORIGINAL code could not be found in the file, so you were interrupted. The text in ORIGINAL must EXACTLY match lines of code. ${problematicCode}`
|
||||
`The edit was not applied. The text in ORIGINAL must EXACTLY match lines of code in the file, but there was no match for:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code matches a code excerpt exactly.`
|
||||
: str === `Not unique` ?
|
||||
`The most recent ORIGINAL code shows up multiple times in the file, so you were interrupted. You might want to expand the ORIGINAL excerpt so it's unique. ${problematicCode}`
|
||||
`The edit was not applied. The text in ORIGINAL must be unique, but the following ORIGINAL code appears multiple times in the file:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code is unique.`
|
||||
: str === 'Has overlap' ?
|
||||
`The most recent ORIGINAL code has overlap with another ORIGINAL code block that you outputted. Do NOT output any overlapping edits. ${problematicCode}`
|
||||
`The edit was not applied. The text in the ORIGINAL blocks must not overlap, but the following ORIGINAL code had overlap with another ORIGINAL string:\n${problematicCode}. Ensure you have the latest version of the file, and ensure the ORIGINAL code blocks do not overlap.`
|
||||
: ``
|
||||
return descStr
|
||||
}
|
||||
|
|
@ -1594,14 +1579,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
if (!model) throw new Error(`Error applying Search/Replace blocks: File does not exist.`)
|
||||
const modelStr = model.getValue(EndOfLinePreference.LF)
|
||||
|
||||
|
||||
const replacements: { origStart: number; origEnd: number; block: ExtractedSearchReplaceBlock }[] = []
|
||||
for (const b of blocks) {
|
||||
const i = modelStr.indexOf(b.orig)
|
||||
if (i === -1)
|
||||
throw new Error(this._errContentOfInvalidStr('Not found', b.orig))
|
||||
const j = modelStr.lastIndexOf(b.orig)
|
||||
if (i !== j)
|
||||
throw new Error(this._errContentOfInvalidStr('Not unique', b.orig))
|
||||
const res = findTextInCode(b.orig, modelStr, true, { returnType: 'indices' })
|
||||
if (typeof res === 'string')
|
||||
throw new Error(this._errContentOfInvalidStr(res, b.orig))
|
||||
const [i, _] = res
|
||||
|
||||
replacements.push({
|
||||
origStart: i,
|
||||
|
|
@ -1772,7 +1756,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
// update stream state to the first line of original if some portion of original has been written
|
||||
if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) {
|
||||
const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line
|
||||
const originalRange = findTextInCode(block.orig, originalFileCode, false, startingAtLine)
|
||||
const originalRange = findTextInCode(block.orig, originalFileCode, false, { startingAtLine, returnType: 'lines' })
|
||||
if (typeof originalRange !== 'string') {
|
||||
const [startLine, _] = convertOriginalRangeToFinalRange(originalRange)
|
||||
diffZone._streamState.line = startLine
|
||||
|
|
@ -1798,7 +1782,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
// if this is the first time we're seeing this block, add it as a diffarea so we can start streaming in it
|
||||
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
|
||||
|
||||
const originalBounds = findTextInCode(block.orig, originalFileCode, true)
|
||||
const originalBounds = findTextInCode(block.orig, originalFileCode, true, { returnType: 'lines' })
|
||||
// if error
|
||||
// Check for overlap with existing modified ranges
|
||||
const hasOverlap = addedTrackingZoneOfBlockNum.some(trackingZone => {
|
||||
|
|
|
|||
|
|
@ -832,55 +832,39 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters<Res
|
|||
}
|
||||
else if (toolMessage.type === 'success' || toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') {
|
||||
// add apply box
|
||||
if (params) {
|
||||
const applyBoxId = getApplyBoxId({
|
||||
threadId: threadId,
|
||||
messageIdx: messageIdx,
|
||||
tokenIdx: 'N/A',
|
||||
})
|
||||
|
||||
componentParams.desc2 = <EditToolHeaderButtons
|
||||
applyBoxId={applyBoxId}
|
||||
uri={params.uri}
|
||||
codeStr={content}
|
||||
/>
|
||||
}
|
||||
const applyBoxId = getApplyBoxId({
|
||||
threadId: threadId,
|
||||
messageIdx: messageIdx,
|
||||
tokenIdx: 'N/A',
|
||||
})
|
||||
componentParams.desc2 = <EditToolHeaderButtons
|
||||
applyBoxId={applyBoxId}
|
||||
uri={params.uri}
|
||||
codeStr={content}
|
||||
/>
|
||||
|
||||
// add children
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
code={content}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
|
||||
if (toolMessage.type !== 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
|
||||
componentParams.bottomChildren = <EditToolLintErrors lintErrors={result?.lintErrors || []} />
|
||||
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
code={content}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
componentParams.bottomChildren = <BottomChildren title='Lint errors'>
|
||||
{result?.lintErrors?.map((error, i) => (
|
||||
<div key={i} className="">Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
|
||||
))}
|
||||
</BottomChildren>
|
||||
}
|
||||
else {
|
||||
// error
|
||||
const { result } = toolMessage
|
||||
if (params) {
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
{/* error */}
|
||||
<CodeChildren>
|
||||
{result}
|
||||
</CodeChildren>
|
||||
|
||||
{/* content */}
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
code={content}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
componentParams.children = <CodeChildren>
|
||||
{result}
|
||||
</CodeChildren>
|
||||
}
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
{result}
|
||||
</BottomChildren>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1063,87 +1047,87 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, curr
|
|||
setSelections={setStagingSelections}
|
||||
>
|
||||
<VoidInputBox2
|
||||
enableAtToMention
|
||||
ref={setTextAreaRef}
|
||||
className='min-h-[81px] max-h-[500px] px-0.5'
|
||||
placeholder="Edit your message..."
|
||||
onChangeText={(text) => setIsDisabled(!text)}
|
||||
onFocus={() => {
|
||||
setIsFocused(true)
|
||||
chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsFocused(false)
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</VoidChatArea>
|
||||
}
|
||||
enableAtToMention
|
||||
ref={setTextAreaRef}
|
||||
className='min-h-[81px] max-h-[500px] px-0.5'
|
||||
placeholder="Edit your message..."
|
||||
onChangeText={(text) => setIsDisabled(!text)}
|
||||
onFocus={() => {
|
||||
setIsFocused(true)
|
||||
chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsFocused(false)
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</VoidChatArea>
|
||||
}
|
||||
|
||||
const isMsgAfterCheckpoint = currCheckpointIdx !== undefined && currCheckpointIdx === messageIdx - 1
|
||||
const isMsgAfterCheckpoint = currCheckpointIdx !== undefined && currCheckpointIdx === messageIdx - 1
|
||||
|
||||
return <div
|
||||
// align chatbubble accoridng to role
|
||||
className={`
|
||||
return <div
|
||||
// align chatbubble accoridng to role
|
||||
className={`
|
||||
relative ml-auto
|
||||
${mode === 'edit' ? 'w-full max-w-full'
|
||||
: mode === 'display' ? `self-end w-fit max-w-full whitespace-pre-wrap` : '' // user words should be pre
|
||||
}
|
||||
: mode === 'display' ? `self-end w-fit max-w-full whitespace-pre-wrap` : '' // user words should be pre
|
||||
}
|
||||
|
||||
${isCheckpointGhost && !isMsgAfterCheckpoint ? 'opacity-50 pointer-events-none' : ''}
|
||||
`}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div
|
||||
// style chatbubble according to role
|
||||
className={`
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div
|
||||
// style chatbubble according to role
|
||||
className={`
|
||||
text-left rounded-lg max-w-full
|
||||
${mode === 'edit' ? ''
|
||||
: mode === 'display' ? 'p-2 flex flex-col bg-void-bg-1 text-void-fg-1 overflow-x-auto cursor-pointer' : ''
|
||||
}
|
||||
: mode === 'display' ? 'p-2 flex flex-col bg-void-bg-1 text-void-fg-1 overflow-x-auto cursor-pointer' : ''
|
||||
}
|
||||
`}
|
||||
onClick={() => { if (mode === 'display') { onOpenEdit() } }}
|
||||
>
|
||||
{chatbubbleContents}
|
||||
</div>
|
||||
onClick={() => { if (mode === 'display') { onOpenEdit() } }}
|
||||
>
|
||||
{chatbubbleContents}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div
|
||||
className="absolute -top-1 -right-1 translate-x-0 -translate-y-0 z-1"
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-content='Edit message'
|
||||
// data-tooltip-place='left'
|
||||
>
|
||||
<EditSymbol
|
||||
size={18}
|
||||
className={`
|
||||
<div
|
||||
className="absolute -top-1 -right-1 translate-x-0 -translate-y-0 z-1"
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-content='Edit message'
|
||||
// data-tooltip-place='left'
|
||||
>
|
||||
<EditSymbol
|
||||
size={18}
|
||||
className={`
|
||||
cursor-pointer
|
||||
p-[2px]
|
||||
bg-void-bg-1 border border-void-border-1 rounded-md
|
||||
transition-opacity duration-200 ease-in-out
|
||||
${isHovered || (isFocused && mode === 'edit') ? 'opacity-100' : 'opacity-0'}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (mode === 'display') {
|
||||
onOpenEdit()
|
||||
} else if (mode === 'edit') {
|
||||
onCloseEdit()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
onClick={() => {
|
||||
if (mode === 'display') {
|
||||
onOpenEdit()
|
||||
} else if (mode === 'edit') {
|
||||
onCloseEdit()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
const SmallProseWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className='
|
||||
return <div className='
|
||||
text-void-fg-4
|
||||
prose
|
||||
prose-sm
|
||||
|
|
@ -1200,12 +1184,12 @@ prose-pre:my-2
|
|||
|
||||
prose-table:text-[13px]
|
||||
'>
|
||||
{children}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
||||
const ProseWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className='
|
||||
return <div className='
|
||||
text-void-fg-2
|
||||
prose
|
||||
prose-sm
|
||||
|
|
@ -1230,77 +1214,77 @@ prose-ul:leading-normal
|
|||
|
||||
max-w-none
|
||||
'
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
const AssistantMessageComponent = ({ chatMessage, isCheckpointGhost, isCommitted, messageIdx }: { chatMessage: ChatMessage & { role: 'assistant' }, isCheckpointGhost: boolean, messageIdx: number, isCommitted: boolean }) => {
|
||||
|
||||
const accessor = useAccessor()
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
const accessor = useAccessor()
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
|
||||
const reasoningStr = chatMessage.reasoning?.trim() || null
|
||||
const hasReasoning = !!reasoningStr
|
||||
const isDoneReasoning = !!chatMessage.displayContent
|
||||
const thread = chatThreadsService.getCurrentThread()
|
||||
const reasoningStr = chatMessage.reasoning?.trim() || null
|
||||
const hasReasoning = !!reasoningStr
|
||||
const isDoneReasoning = !!chatMessage.displayContent
|
||||
const thread = chatThreadsService.getCurrentThread()
|
||||
|
||||
|
||||
const chatMessageLocation: ChatMessageLocation = {
|
||||
threadId: thread.id,
|
||||
messageIdx: messageIdx,
|
||||
}
|
||||
const chatMessageLocation: ChatMessageLocation = {
|
||||
threadId: thread.id,
|
||||
messageIdx: messageIdx,
|
||||
}
|
||||
|
||||
const isEmpty = !chatMessage.displayContent && !chatMessage.reasoning
|
||||
if (isEmpty) return null
|
||||
const isEmpty = !chatMessage.displayContent && !chatMessage.reasoning
|
||||
if (isEmpty) return null
|
||||
|
||||
return <>
|
||||
{/* reasoning token */}
|
||||
{hasReasoning &&
|
||||
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
|
||||
<ReasoningWrapper isDoneReasoning={isDoneReasoning} isStreaming={!isCommitted}>
|
||||
<SmallProseWrapper>
|
||||
<ChatMarkdownRender
|
||||
string={reasoningStr}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
isApplyEnabled={false}
|
||||
isLinkDetectionEnabled={true}
|
||||
/>
|
||||
</SmallProseWrapper>
|
||||
</ReasoningWrapper>
|
||||
</div>
|
||||
}
|
||||
return <>
|
||||
{/* reasoning token */}
|
||||
{hasReasoning &&
|
||||
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
|
||||
<ReasoningWrapper isDoneReasoning={isDoneReasoning} isStreaming={!isCommitted}>
|
||||
<SmallProseWrapper>
|
||||
<ChatMarkdownRender
|
||||
string={reasoningStr}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
isApplyEnabled={false}
|
||||
isLinkDetectionEnabled={true}
|
||||
/>
|
||||
</SmallProseWrapper>
|
||||
</ReasoningWrapper>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* assistant message */}
|
||||
{chatMessage.displayContent &&
|
||||
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
|
||||
<ProseWrapper>
|
||||
<ChatMarkdownRender
|
||||
string={chatMessage.displayContent || ''}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
isApplyEnabled={true}
|
||||
isLinkDetectionEnabled={true}
|
||||
/>
|
||||
</ProseWrapper>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
{/* assistant message */}
|
||||
{chatMessage.displayContent &&
|
||||
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
|
||||
<ProseWrapper>
|
||||
<ChatMarkdownRender
|
||||
string={chatMessage.displayContent || ''}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
isApplyEnabled={true}
|
||||
isLinkDetectionEnabled={true}
|
||||
/>
|
||||
</ProseWrapper>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
const ReasoningWrapper = ({ isDoneReasoning, isStreaming, children }: { isDoneReasoning: boolean, isStreaming: boolean, children: React.ReactNode }) => {
|
||||
const isDone = isDoneReasoning || !isStreaming
|
||||
const isWriting = !isDone
|
||||
const [isOpen, setIsOpen] = useState(isWriting)
|
||||
useEffect(() => {
|
||||
if (!isWriting) setIsOpen(false) // if just finished reasoning, close
|
||||
}, [isWriting])
|
||||
return <ToolHeaderWrapper title='Reasoning' desc1={isWriting ? <IconLoading /> : ''} isOpen={isOpen} onClick={() => setIsOpen(v => !v)}>
|
||||
<ToolChildrenWrapper>
|
||||
<div className='!select-text cursor-auto'>
|
||||
{children}
|
||||
</div>
|
||||
</ToolChildrenWrapper>
|
||||
</ToolHeaderWrapper>
|
||||
const isDone = isDoneReasoning || !isStreaming
|
||||
const isWriting = !isDone
|
||||
const [isOpen, setIsOpen] = useState(isWriting)
|
||||
useEffect(() => {
|
||||
if (!isWriting) setIsOpen(false) // if just finished reasoning, close
|
||||
}, [isWriting])
|
||||
return <ToolHeaderWrapper title='Reasoning' desc1={isWriting ? <IconLoading /> : ''} isOpen={isOpen} onClick={() => setIsOpen(v => !v)}>
|
||||
<ToolChildrenWrapper>
|
||||
<div className='!select-text cursor-auto'>
|
||||
{children}
|
||||
</div>
|
||||
</ToolChildrenWrapper>
|
||||
</ToolHeaderWrapper>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1309,10 +1293,10 @@ return <ToolHeaderWrapper title='Reasoning' desc1={isWriting ? <IconLoading /> :
|
|||
// should either be past or "-ing" tense, not present tense. Eg. when the LLM searches for something, the user expects it to say "I searched for X" or "I am searching for X". Not "I search X".
|
||||
|
||||
const loadingTitleWrapper = (item: React.ReactNode): React.ReactNode => {
|
||||
return <span className='flex items-center flex-nowrap'>
|
||||
{item}
|
||||
<IconLoading className='w-3 text-sm' />
|
||||
</span>
|
||||
return <span className='flex items-center flex-nowrap'>
|
||||
{item}
|
||||
<IconLoading className='w-3 text-sm' />
|
||||
</span>
|
||||
}
|
||||
|
||||
const titleOfToolName = {
|
||||
|
|
@ -1422,7 +1406,7 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
|
|||
const toolParams = _toolParams as ToolCallParams['run_command']
|
||||
return {
|
||||
desc1: `"${toolParams.command}"`,
|
||||
}
|
||||
}
|
||||
},
|
||||
'run_persistent_command': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['run_persistent_command']
|
||||
|
|
@ -1577,8 +1561,8 @@ const LintErrorChildren = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
|
|||
</div>
|
||||
}
|
||||
|
||||
const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) => {
|
||||
if (lintErrors.length === 0) return null;
|
||||
const BottomChildren = ({ children, title }: { children: React.ReactNode, title: string }) => {
|
||||
if (!children) return null;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<div className="w-full px-2 mt-0.5">
|
||||
|
|
@ -1590,15 +1574,13 @@ const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) =>
|
|||
<ChevronRight
|
||||
className={`mr-1 h-3 w-3 flex-shrink-0 transition-transform duration-100 text-void-fg-4 group-hover:text-void-fg-3 ${isOpen ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
<span className="font-medium text-void-fg-4 group-hover:text-void-fg-3 text-xs">Lint errors</span>
|
||||
<span className="font-medium text-void-fg-4 group-hover:text-void-fg-3 text-xs">{title}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'opacity-100' : 'max-h-0 opacity-0'} text-xs pl-4`}
|
||||
>
|
||||
<div className="flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap text-void-fg-4 opacity-90 border-l-2 border-void-warning px-2 py-0.5">
|
||||
{lintErrors.map((error, i) => (
|
||||
<div key={i} className="">Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
|
||||
))}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2178,7 +2160,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
return <ToolHeaderWrapper {...componentParams} />
|
||||
}
|
||||
},
|
||||
'rewrite_file': {
|
||||
'rewrite_file': {
|
||||
resultWrapper: (params) => {
|
||||
return <EditTool {...params} content={`${'```\n'}${params.toolMessage.params.newContent}${'\n```'}`} />
|
||||
}
|
||||
|
|
@ -2202,7 +2184,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
return <CommandTool {...params} type='run_persistent_command' />
|
||||
}
|
||||
},
|
||||
'open_persistent_terminal': {
|
||||
'open_persistent_terminal': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
|
|
@ -2795,7 +2777,7 @@ export const SidebarChat = () => {
|
|||
|
||||
const sidebarRef = useRef<HTMLDivElement>(null)
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const onSubmit = useCallback(async (_forceSubmit?: string) => {
|
||||
const onSubmit = useCallback(async (_forceSubmit?: string) => {
|
||||
|
||||
if (isDisabled && !_forceSubmit) return
|
||||
if (isRunning) return
|
||||
|
|
@ -2817,7 +2799,7 @@ export const SidebarChat = () => {
|
|||
|
||||
}, [chatThreadsService, isDisabled, isRunning, textAreaRef, textAreaFnsRef, setSelections, settingsState])
|
||||
|
||||
const onAbort = async () => {
|
||||
const onAbort = async () => {
|
||||
const threadId = currentThread.id
|
||||
await chatThreadsService.abortRunning(threadId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,12 +295,14 @@ export class ToolsService implements IToolsService {
|
|||
contents = model.getValueInRange({ startLineNumber, startColumn: 1, endLineNumber, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF)
|
||||
}
|
||||
|
||||
const totalNumLines = model.getLineCount()
|
||||
|
||||
const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1)
|
||||
const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1
|
||||
const fileContents = contents.slice(fromIdx, toIdx + 1) // paginate
|
||||
const hasNextPage = (contents.length - 1) - toIdx >= 1
|
||||
const totalFileLen = contents.length
|
||||
return { result: { fileContents, totalFileLen, hasNextPage } }
|
||||
return { result: { fileContents, totalFileLen, hasNextPage, totalNumLines } }
|
||||
},
|
||||
|
||||
ls_dir: async ({ uri, pageNumber }) => {
|
||||
|
|
@ -414,7 +416,6 @@ export class ToolsService implements IToolsService {
|
|||
if (this.commandBarService.getStreamState(uri) === 'streaming') {
|
||||
throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`)
|
||||
}
|
||||
console.log('aaaa', searchReplaceBlocks)
|
||||
editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks })
|
||||
|
||||
// at end, get lint errors
|
||||
|
|
@ -460,7 +461,7 @@ export class ToolsService implements IToolsService {
|
|||
// given to the LLM after the call for successful tool calls
|
||||
this.stringOfResult = {
|
||||
read_file: (params, result) => {
|
||||
return `${params.uri.fsPath}\n\`\`\`\n${result.fileContents}\n\`\`\`${nextPageStr(result.hasNextPage)}`
|
||||
return `${params.uri.fsPath}\n\`\`\`\n${result.fileContents}\n\`\`\`${nextPageStr(result.hasNextPage)}${result.hasNextPage ? `\nMore info because truncated: this file has ${result.totalNumLines} lines, or ${result.totalFileLen} characters.` : ''}`
|
||||
},
|
||||
ls_dir: (params, result) => {
|
||||
const dirTreeStr = stringifyDirectoryTree1Deep(params, result)
|
||||
|
|
@ -522,7 +523,7 @@ export class ToolsService implements IToolsService {
|
|||
}
|
||||
// normal command
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `${result_}\nTerminal command ran, but was interrupted by Void after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not necessarily finish successfully. To try with more time, open a persistent terminal and run the command there.`
|
||||
return `${result_}\nTerminal command ran, but was automatically killed by Void after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not finish successfully. To try with more time, open a persistent terminal and run the command there.`
|
||||
}
|
||||
throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import { Color, RGBA } from '../../../../../base/common/color.js';
|
||||
import { registerColor } from '../../../../../platform/theme/common/colorUtils.js';
|
||||
|
||||
|
||||
// Widget colors
|
||||
export const acceptBg = '#1a7431'
|
||||
export const acceptAllBg = '#1e8538'
|
||||
export const acceptBorder = '1px solid #145626'
|
||||
|
|
@ -6,3 +11,23 @@ export const rejectAllBg = '#cf2838'
|
|||
export const rejectBorder = '1px solid #8e1c27'
|
||||
export const buttonFontSize = '11px'
|
||||
export const buttonTextColor = 'white'
|
||||
|
||||
|
||||
// editCodeService colors
|
||||
export const greenBG = new Color(new RGBA(155, 185, 85, .1)); // default is RGBA(155, 185, 85, .2)
|
||||
export const redBG = new Color(new RGBA(255, 0, 0, .1)); // default is RGBA(255, 0, 0, .2)
|
||||
export const sweepBG = new Color(new RGBA(100, 100, 100, .2));
|
||||
export const highlightBG = new Color(new RGBA(100, 100, 100, .1));
|
||||
export const sweepIdxBG = new Color(new RGBA(100, 100, 100, .5));
|
||||
|
||||
|
||||
const configOfBG = (color: Color) => {
|
||||
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
||||
}
|
||||
|
||||
// gets converted to --vscode-void-greenBG, see void.css, asCssVariable
|
||||
registerColor('void.greenBG', configOfBG(greenBG), '', true);
|
||||
registerColor('void.redBG', configOfBG(redBG), '', true);
|
||||
registerColor('void.sweepBG', configOfBG(sweepBG), '', true);
|
||||
registerColor('void.highlightBG', configOfBG(highlightBG), '', true);
|
||||
registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true);
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export type VoidStaticModelInfo = { // not stateful
|
|||
}
|
||||
|
||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special paramete
|
||||
specialToolFormat?: 'openai-style' | 'anthropic-style', // null defaults to XML
|
||||
specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style', // null defaults to XML
|
||||
supportsFIM: boolean;
|
||||
|
||||
reasoningCapabilities: false | {
|
||||
|
|
@ -648,6 +648,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-2.5-pro-exp-03-25': {
|
||||
|
|
@ -657,6 +658,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-2.0-flash': {
|
||||
|
|
@ -666,6 +668,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-2.0-flash-lite-preview-02-05': {
|
||||
|
|
@ -675,6 +678,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-1.5-flash': {
|
||||
|
|
@ -684,6 +688,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-1.5-pro': {
|
||||
|
|
@ -693,6 +698,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-1.5-flash-8b': {
|
||||
|
|
@ -702,6 +708,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
specialToolFormat: 'gemini-style',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
|
|
|||
|
|
@ -212,8 +212,8 @@ export const voidTools
|
|||
description: `Returns full contents of a given file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
start_line: { description: 'Optional. Do NOT fill this in unless you already know the line numbers you need to search. Defaults to 1.' },
|
||||
end_line: { description: 'Optional. Do NOT fill this in unless you already know the line numbers you need to search. Defaults to Infinity.' },
|
||||
start_line: { description: 'Optional. Do NOT fill this field in unless you were specifically given exact line numbers to search. Defaults to the beginning of the file.' },
|
||||
end_line: { description: 'Optional. Do NOT fill this field in unless you were specifically given exact line numbers to search. Defaults to the end of the file.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
|
@ -275,7 +275,7 @@ export const voidTools
|
|||
|
||||
read_lint_errors: {
|
||||
name: 'read_lint_errors',
|
||||
description: `Returns all lint errors on a given file.`,
|
||||
description: `Use this tool to view all the lint errors on a file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -51,8 +51,22 @@ export type OpenAILLMChatMessage = {
|
|||
content: string;
|
||||
tool_call_id: string;
|
||||
}
|
||||
export type LLMChatMessage = AnthropicLLMChatMessage | OpenAILLMChatMessage
|
||||
|
||||
export type GeminiLLMChatMessage = {
|
||||
role: 'model'
|
||||
parts: (
|
||||
| { text: string; }
|
||||
| { functionCall: { name: ToolName, args: object } }
|
||||
)[];
|
||||
} | {
|
||||
role: 'user';
|
||||
parts: (
|
||||
| { text: string; }
|
||||
| { functionResponse: { name: ToolName, response: { result: string } } }
|
||||
)[];
|
||||
}
|
||||
|
||||
export type LLMChatMessage = AnthropicLLMChatMessage | OpenAILLMChatMessage | GeminiLLMChatMessage
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export type ToolCallParams = {
|
|||
|
||||
// RESULT OF TOOL CALL
|
||||
export type ToolResultType = {
|
||||
'read_file': { fileContents: string, totalFileLen: number, hasNextPage: boolean },
|
||||
'read_file': { fileContents: string, totalFileLen: number, totalNumLines: number, hasNextPage: boolean },
|
||||
'ls_dir': { children: ShallowDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
|
||||
'get_dir_tree': { str: string, },
|
||||
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Ollama } from 'ollama';
|
|||
import OpenAI, { ClientOptions } from 'openai';
|
||||
import { MistralCore } from '@mistralai/mistralai/core.js';
|
||||
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
||||
import { GoogleGenerativeAI, Tool as GeminiTool, SchemaType, FunctionDeclaration, FunctionDeclarationSchemaProperty } from '@google/generative-ai';
|
||||
// import { GoogleAuth } from 'google-auth-library'
|
||||
/* eslint-enable */
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderNam
|
|||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js';
|
||||
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
|
||||
import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js';
|
||||
import { generateUuid } from '../../../../../base/common/uuid.js';
|
||||
|
||||
|
||||
|
||||
|
|
@ -33,7 +35,11 @@ type InternalCommonMessageParams = {
|
|||
_setAborter: (aborter: () => void) => void;
|
||||
}
|
||||
|
||||
type SendChatParams_Internal = InternalCommonMessageParams & { messages: LLMChatMessage[]; separateSystemMessage: string | undefined; chatMode: ChatMode | null; }
|
||||
type SendChatParams_Internal = InternalCommonMessageParams & {
|
||||
messages: LLMChatMessage[];
|
||||
separateSystemMessage: string | undefined;
|
||||
chatMode: ChatMode | null;
|
||||
}
|
||||
type SendFIMParams_Internal = InternalCommonMessageParams & { messages: LLMFIMMessage; separateSystemMessage: string | undefined; }
|
||||
export type ListParams_Internal<ModelResponse> = ModelListParams<ModelResponse>
|
||||
|
||||
|
|
@ -42,13 +48,6 @@ const invalidApiKeyMessage = (providerName: ProviderName) => `Invalid ${displayI
|
|||
|
||||
// ------------ OPENAI-COMPATIBLE (HELPERS) ------------
|
||||
|
||||
// const getGoogleApiKey = async () => {
|
||||
// // module‑level singleton
|
||||
// const auth = new GoogleAuth({ scopes: `https://www.googleapis.com/auth/cloud-platform` });
|
||||
// const key = await auth.getAccessToken()
|
||||
// if (!key) throw new Error(`Google API failed to generate a key.`)
|
||||
// return key
|
||||
// }
|
||||
|
||||
|
||||
const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includeInPayload }: { settingsOfProvider: SettingsOfProvider, providerName: ProviderName, includeInPayload?: { [s: string]: any } }) => {
|
||||
|
|
@ -88,10 +87,6 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
|
|||
...commonPayloadOpts,
|
||||
})
|
||||
}
|
||||
else if (providerName === 'gemini') {
|
||||
const thisConfig = settingsOfProvider[providerName]
|
||||
return new OpenAI({ baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
|
||||
}
|
||||
// else if (providerName === 'googleVertex') {
|
||||
// // https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library
|
||||
// const thisConfig = settingsOfProvider[providerName]
|
||||
|
|
@ -131,6 +126,7 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
|
|||
|
||||
|
||||
const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, }: SendFIMParams_Internal) => {
|
||||
|
||||
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
|
||||
if (!supportsFIM) {
|
||||
if (modelName === modelName_)
|
||||
|
|
@ -193,7 +189,7 @@ const openAITools = (chatMode: ChatMode) => {
|
|||
return openAITools
|
||||
}
|
||||
|
||||
const openAIToolToRawToolCallObj = (name: string, toolParamsStr: string, id: string): RawToolCallObj | null => {
|
||||
const rawToolCallObjOf = (name: string, toolParamsStr: string, id: string): RawToolCallObj | null => {
|
||||
if (!isAToolName(name)) return null
|
||||
const rawParams: RawToolParamsObj = {}
|
||||
let input: unknown
|
||||
|
|
@ -297,6 +293,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
|
|||
fullReasoningSoFar += newReasoning
|
||||
}
|
||||
|
||||
// call onText
|
||||
onText({
|
||||
fullText: fullTextSoFar,
|
||||
fullReasoning: fullReasoningSoFar,
|
||||
|
|
@ -309,7 +306,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
|
|||
onError({ message: 'Void: Response from model was empty.', fullError: null })
|
||||
}
|
||||
else {
|
||||
const toolCall = openAIToolToRawToolCallObj(toolName, toolParamsStr, toolId)
|
||||
const toolCall = rawToolCallObjOf(toolName, toolParamsStr, toolId)
|
||||
const toolCallObj = toolCall ? { toolCall } : {}
|
||||
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null, ...toolCallObj });
|
||||
}
|
||||
|
|
@ -438,7 +435,7 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag
|
|||
|
||||
})
|
||||
|
||||
// manually parse out tool results
|
||||
// manually parse out tool results if XML
|
||||
if (!specialToolFormat) {
|
||||
const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode)
|
||||
onText = newOnText
|
||||
|
|
@ -544,6 +541,7 @@ const sendMistralFIM = ({ messages, onFinalMessage, onError, settingsOfProvider,
|
|||
stop: messages.stopTokens,
|
||||
})
|
||||
.then(async response => {
|
||||
|
||||
// unfortunately, _setAborter() does not exist
|
||||
let content = response?.ok ? response.value.choices?.[0]?.message?.content ?? '' : '';
|
||||
const fullText = typeof content === 'string' ? content
|
||||
|
|
@ -620,6 +618,165 @@ const sendOllamaFIM = ({ messages, onFinalMessage, onError, settingsOfProvider,
|
|||
})
|
||||
}
|
||||
|
||||
// ---------------- GEMINI NATIVE IMPLEMENTATION ----------------
|
||||
|
||||
|
||||
|
||||
const toGeminiFunctionDecl = (toolInfo: InternalToolInfo) => {
|
||||
const { name, description, params } = toolInfo
|
||||
const paramsWithType: { [k: string]: FunctionDeclarationSchemaProperty } = {}
|
||||
for (const key in params) {
|
||||
paramsWithType[key] = { type: SchemaType.STRING, ...params[key] }
|
||||
}
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
parameters: {
|
||||
type: SchemaType.OBJECT,
|
||||
properties: paramsWithType,
|
||||
}
|
||||
} satisfies FunctionDeclaration
|
||||
}
|
||||
|
||||
|
||||
const geminiTools = (chatMode: ChatMode): GeminiTool[] | null => {
|
||||
const allowedTools = availableTools(chatMode)
|
||||
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
|
||||
const functionDecls: FunctionDeclaration[] = []
|
||||
for (const t in allowedTools ?? {}) {
|
||||
functionDecls.push(toGeminiFunctionDecl(allowedTools[t]))
|
||||
}
|
||||
const tools: GeminiTool = { functionDeclarations: functionDecls, }
|
||||
return [tools]
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Implementation for Gemini using Google's native API
|
||||
const sendGeminiChat = async ({
|
||||
messages,
|
||||
separateSystemMessage,
|
||||
onText,
|
||||
onFinalMessage,
|
||||
onError,
|
||||
settingsOfProvider,
|
||||
modelName: modelName_,
|
||||
_setAborter,
|
||||
providerName,
|
||||
modelSelectionOptions,
|
||||
chatMode,
|
||||
}: SendChatParams_Internal) => {
|
||||
|
||||
console.log('MESSAGES', JSON.stringify(messages, null, 2))
|
||||
|
||||
if (providerName !== 'gemini') throw new Error(`Sending Gemini chat, but provider was ${providerName}`)
|
||||
|
||||
const thisConfig = settingsOfProvider[providerName]
|
||||
|
||||
const {
|
||||
modelName,
|
||||
specialToolFormat,
|
||||
// reasoningCapabilities,
|
||||
} = getModelCapabilities(providerName, modelName_)
|
||||
|
||||
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
|
||||
|
||||
// reasoning
|
||||
// const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {}
|
||||
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
|
||||
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
|
||||
|
||||
// tools
|
||||
const potentialTools = chatMode !== null ? geminiTools(chatMode) : null
|
||||
const nativeToolsObj = potentialTools && specialToolFormat === 'gemini-style' ?
|
||||
{ tools: potentialTools } as const
|
||||
: {}
|
||||
|
||||
// instance
|
||||
const genAI = new GoogleGenerativeAI(
|
||||
thisConfig.apiKey
|
||||
);
|
||||
const model = genAI.getGenerativeModel({
|
||||
systemInstruction: separateSystemMessage,
|
||||
model: modelName,
|
||||
});
|
||||
|
||||
// manually parse out tool results if XML
|
||||
if (!specialToolFormat) {
|
||||
const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode)
|
||||
onText = newOnText
|
||||
onFinalMessage = newOnFinalMessage
|
||||
}
|
||||
|
||||
// when receive text
|
||||
let fullReasoningSoFar = ''
|
||||
let fullTextSoFar = ''
|
||||
|
||||
let toolName = ''
|
||||
let toolParamsStr = ''
|
||||
|
||||
model.generateContentStream({
|
||||
systemInstruction: separateSystemMessage ?? undefined,
|
||||
contents: messages as any,
|
||||
...includeInPayload,
|
||||
...nativeToolsObj,
|
||||
})
|
||||
.then(async ({ stream, response }) => {
|
||||
_setAborter(() => { stream.return(fullTextSoFar); });
|
||||
|
||||
// Process the stream
|
||||
for await (const chunk of stream) {
|
||||
// message
|
||||
const newText = chunk.text() ?? ''
|
||||
fullTextSoFar += newText
|
||||
|
||||
// tool call
|
||||
const functionCalls = chunk.functionCalls()
|
||||
if (functionCalls && functionCalls.length > 0) {
|
||||
const functionCall = functionCalls[0] // Get the first function call
|
||||
toolName = functionCall.name ?? ''
|
||||
toolParamsStr = JSON.stringify(functionCall.args ?? {})
|
||||
}
|
||||
|
||||
// (do not handle reasoning yet)
|
||||
|
||||
// call onText
|
||||
onText({
|
||||
fullText: fullTextSoFar,
|
||||
fullReasoning: fullReasoningSoFar,
|
||||
toolCall: isAToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: 'dummy' } : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// on final
|
||||
if (!fullTextSoFar && !fullReasoningSoFar && !toolName) {
|
||||
onError({ message: 'Void: Response from model was empty.', fullError: null })
|
||||
} else {
|
||||
const toolId = generateUuid() // gemini does not generate tool IDs. Generate one
|
||||
const toolCall = rawToolCallObjOf(toolName, toolParamsStr, toolId)
|
||||
const toolCallObj = toolCall ? { toolCall } : {}
|
||||
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null, ...toolCallObj });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
const message = error?.message
|
||||
if (typeof message === 'string') {
|
||||
|
||||
if (error.message?.includes('API key')) {
|
||||
onError({ message: invalidApiKeyMessage(providerName), fullError: error });
|
||||
}
|
||||
else if (error?.message?.includes('429')) {
|
||||
onError({ message: 'Rate limit reached. ' + error, fullError: error });
|
||||
}
|
||||
else
|
||||
onError({ message: error + '', fullError: error });
|
||||
}
|
||||
else {
|
||||
onError({ message: error + '', fullError: error });
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
type CallFnOfProvider = {
|
||||
|
|
@ -647,7 +804,7 @@ export const sendLLMMessageToProviderImplementation = {
|
|||
list: null,
|
||||
},
|
||||
gemini: {
|
||||
sendChat: (params) => _sendOpenAICompatibleChat(params),
|
||||
sendChat: (params) => sendGeminiChat(params),
|
||||
sendFIM: null,
|
||||
list: null,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -33,13 +33,6 @@ export const sendLLMMessage = async ({
|
|||
// only captures number of messages and message "shape", no actual code, instructions, prompts, etc
|
||||
const captureLLMEvent = (eventId: string, extras?: object) => {
|
||||
|
||||
let totalTokens = 0
|
||||
if (messagesType === 'chatMessages') {
|
||||
for (const m of messages_) totalTokens += m.content.length
|
||||
}
|
||||
else {
|
||||
totalTokens = messages_.prefix.length + messages_.suffix.length
|
||||
}
|
||||
|
||||
metricsService.capture(eventId, {
|
||||
providerName,
|
||||
|
|
@ -48,13 +41,10 @@ export const sendLLMMessage = async ({
|
|||
numModelsAtEndpoint: settingsOfProvider[providerName]?.models?.length,
|
||||
...messagesType === 'chatMessages' ? {
|
||||
numMessages: messages_?.length,
|
||||
messagesShape: messages_?.map(msg => ({ role: msg.role, length: msg.content.length })),
|
||||
|
||||
} : messagesType === 'FIMMessage' ? {
|
||||
prefixLength: messages_.prefix.length,
|
||||
suffixLength: messages_.suffix.length,
|
||||
} : {},
|
||||
totalTokens,
|
||||
...loggingExtras,
|
||||
...extras,
|
||||
})
|
||||
|
|
@ -103,7 +93,7 @@ export const sendLLMMessage = async ({
|
|||
|
||||
|
||||
if (messagesType === 'chatMessages')
|
||||
captureLLMEvent(`${loggingName} - Sending Message`, { userMessageLength: messages_?.[messages_.length - 1]?.content.length })
|
||||
captureLLMEvent(`${loggingName} - Sending Message`, {})
|
||||
else if (messagesType === 'FIMMessage')
|
||||
captureLLMEvent(`${loggingName} - Sending FIM`, { prefixLen: messages_?.prefix?.length, suffixLen: messages_?.suffix?.length })
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue