diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 98900354..98aad294 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -14,7 +14,7 @@ import { IRange } from '../../../../editor/common/core/range.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { chat_userMessage, chat_systemMessage } from './prompt/prompts.js'; -import { InternalToolInfo, IToolsService, ToolName, voidTools } from '../common/toolsService.js'; +import { InternalToolInfo, IToolsService, ToolFns, ToolName, voidTools } from '../common/toolsService.js'; import { toLLMChatMessage } from '../common/llmMessageTypes.js'; // one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text) @@ -57,11 +57,6 @@ export type ChatMessage = staging: StagingInfo | null } | { role: 'assistant'; - tool_calls?: { - name: string, - id: string, - params: string - }[]; content: string | null; // content received from LLM - allowed to be '', will be replaced with (empty) displayContent: string | null; // content displayed to user (this is the same as content for now) - allowed to be '', will be ignored } | { @@ -327,25 +322,43 @@ class ChatThreadService extends Disposable implements IChatThreadService { this._setStreamState(threadId, { messageSoFar: fullText }) }, onFinalMessage: async ({ fullText, toolCalls }) => { + toolCalls = toolCalls?.filter(tool => tool.name in this._toolsService.toolFns) + console.log('FINAL MESSAGE', fullText, toolCalls) if ((toolCalls?.length ?? 0) === 0) { this._finishStreamingTextMessage(threadId, fullText) } else { - this._addMessageToThread(threadId, { role: 'assistant', content: fullText, displayContent: fullText, tool_calls: toolCalls }) + this._addMessageToThread(threadId, { role: 'assistant', content: fullText, displayContent: fullText }) + this._setStreamState(threadId, { messageSoFar: undefined }) // clear streaming message for (const tool of toolCalls ?? []) { - if (!(tool.name in this._toolsService.toolFns)) { - this._addMessageToThread(threadId, { role: 'tool', name: tool.name, params: tool.params, id: tool.id, content: `Error: This tool was not recognized, so it was not called.`, displayContent: `Error: tool not recognized.`, }) + const toolName = tool.name as ToolName + + // 1. + let toolResult: Awaited> + try { + toolResult = await this._toolsService.toolFns[toolName](tool.params) + } catch (e) { + this._setStreamState(threadId, { error: e }) + shouldContinue = false + break } - else { - const toolName = tool.name as ToolName - const toolResult = await this._toolsService.toolFns[toolName](tool.params) - const string = this._toolsService.toolResultToString[toolName](toolResult as any) // typescript is so bad it doesn't even couple the type of ToolResult with the type of the function being called here - this._addMessageToThread(threadId, { role: 'tool', name: tool.name, params: tool.params, id: tool.id, content: string, displayContent: string, }) - shouldContinue = true + + // 2. + let toolResultStr: string + try { + toolResultStr = this._toolsService.toolResultToString[toolName](toolResult 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 (e) { + this._setStreamState(threadId, { error: e }) + shouldContinue = false + break } + + this._addMessageToThread(threadId, { role: 'tool', name: tool.name, params: tool.params, id: tool.id, content: toolResultStr, displayContent: toolResultStr, }) + shouldContinue = true } + } res_() }, diff --git a/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts b/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts index b4b9d513..9bc3c9bd 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/detectLanguage.ts @@ -1,3 +1,7 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ // eg "bash" -> "shell" export const nameToVscodeLanguage: { [key: string]: string } = { diff --git a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts index 39cd310d..4b9e05da 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts @@ -1,3 +1,8 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + import { URI } from '../../../../../base/common/uri' import { EndOfLinePreference } from '../../../../../editor/common/model' import { IModelService } from '../../../../../editor/common/services/model.js' diff --git a/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts b/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts new file mode 100644 index 00000000..ab1dabad --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/helpers/systemInfo.ts @@ -0,0 +1,17 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { isLinux, isMacintosh, isWindows } from '../../../../../base/common/platform.js'; + +// import { OS, OperatingSystem } from '../../../../../base/common/platform.js'; +// alternatively could use ^ and OS === OperatingSystem.Windows ? ... + + + +export const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null + +export const arch = process.arch +export const osplatform = process.platform; + diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index 415a0c87..105e1e93 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -9,6 +9,7 @@ import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js'; import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js'; import { VSReadFile } from '../helpers/readFile.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; +import { os, arch, osplatform } from '../helpers/systemInfo.js'; // this is just for ease of readability @@ -19,17 +20,22 @@ You are a coding assistant. You are given a list of instructions to follow \`INS Please respond to the user's query. +The user has the following system information: + - ${os} ${arch} ${osplatform} + In the case that the user asks you to make changes to code, you should make sure to return CODE BLOCKS of the changes, as well as explanations and descriptions of the changes. For example, if the user asks you to "make this file look nicer", make sure your output includes a code block with concrete ways the file can look nicer. - Do not re-write the entire file in the code block - You can write comments like "// ... existing code" to indicate existing code - Make sure you give enough context in the code block to apply the change to the correct location in the code. -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. - 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 the examples below. +If you are not given tools, 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 are given tools, you are allowed to use them without asking for permission. You do not have to use them if you don't want to. +If you are given tools, 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. + ## EXAMPLE 1 FILES math.ts diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index cbf8607c..0cfdef04 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -17,6 +17,7 @@ import { env } from '../../../../../../../base/common/process.js' import { ModelDropdown } from './ModelDropdown.js' import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js' import { WarningBox } from './WarningBox.js' +import { os } from '../../../helpers/systemInfo.js' const SubtleButton = ({ onClick, text, icon, disabled }: { onClick: () => void, text: string, icon: React.ReactNode, disabled: boolean }) => { @@ -505,7 +506,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null): Transfe throw new Error(`os '${os}' not recognized`) } -const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null + let transferTheseFiles: TransferFilesInfo = [] let transferError: string | null = null diff --git a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts index 79960826..75cf2739 100644 --- a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts @@ -52,12 +52,12 @@ export type AbortRef = { current: (() => void) | null } export const toLLMChatMessage = (c: ChatMessage): LLMChatMessage => { if (c.role === 'system' || c.role === 'user') { - return { role: c.role, content: c.content ?? '(empty)' } + return { role: c.role, content: c.content || '(empty message)' } } else if (c.role === 'assistant') - return { role: c.role, content: c.content ?? '(empty model output)' } + 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.params, content: c.content ?? '(empty output)' } + return { role: c.role, id: c.id, name: c.name, params: c.params, content: c.content || '(empty output)' } else { throw 1 } diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index 0c7c7f82..2dde2219 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -111,7 +111,7 @@ async function generateDirectoryTreeMd(fileService: IFileService, rootURI: URI): const validateURI = (uriStr: unknown) => { - if (typeof uriStr !== 'string') throw new Error('(uri was not a string)') + if (typeof uriStr !== 'string') throw new Error('(provided uri must be a string)') const uri = URI.file(uriStr) return uri }