Merge remote-tracking branch 'origin/main' into model-selection

This commit is contained in:
Andrew Pareles 2025-04-28 16:55:57 -07:00
commit a5a8559a20
11 changed files with 249 additions and 220 deletions

View file

@ -23,11 +23,11 @@ Most of Void's code lives in the folder `src/vs/workbench/contrib/void/`.
## Building Void
### a. Build Prerequisites - Mac
### a. Mac - Build Prerequisites
If you're using a Mac, you need Python and XCode. You probably have these by default.
### b. Build Prerequisites - Windows
### b. Windows - Build Prerequisites
If you're using a Windows computer, first get [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) (recommended) or [VS Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) (not recommended). If you already have both, you might need to run the next few steps on both of them.
@ -42,7 +42,7 @@ Go to the "Individual Components" tab and select:
Finally, click Install.
### c. Build Prerequisites - Linux
### c. Linux - Build Prerequisites
First, run `npm install -g node-gyp`. Then:
@ -50,27 +50,28 @@ First, run `npm install -g node-gyp`. Then:
- Red Hat (Fedora, etc): `sudo dnf install @development-tools gcc gcc-c++ make libsecret-devel krb5-devel libX11-devel libxkbfile-devel`.
- Others: see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute).
### d. Building Void
### d. Building Void from inside VSCode
To build Void, open `void/` inside VSCode. Then open your terminal and run:
1. `npm install` to install all dependencies.
2. `npm run watchreact` to build Void's browser dependencies like React. (If this doesn't work, try `npm run buildreact`).
3. Build Void.
2. Build Void.
- Press <kbd>Cmd+Shift+B</kbd> (Mac).
- Press <kbd>Ctrl+Shift+B</kbd> (Windows/Linux).
- This step can take ~5 min. The build is done when you see two check marks (one of the items will continue spinning indefinitely - it compiles our React code).
4. Run Void.
3. Run Void.
- Run `./scripts/code.sh` (Mac/Linux).
- Run `./scripts/code.bat` (Windows).
6. Nice-to-knows.
4. Nice-to-knows.
- You can always press <kbd>Ctrl+R</kbd> (<kbd>Cmd+R</kbd>) inside the new window to reload and see your new changes. It's faster than <kbd>Ctrl+Shift+P</kbd> and `Reload Window`.
- You might want to add the flags `--user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions` to the above run command, which lets you delete the `.tmp` folder to reset any IDE changes you made when testing.
- You can kill any of the build scripts by pressing `Ctrl+D` in VSCode terminal. If you press `Ctrl+C` the script will close but will keep running in the background (to open all background scripts, just re-build).
If you get any errors, scroll down for common fixes.
#### Building Void from Terminal
Alternatively, if you want to build Void from the terminal, instead of pressing <kbd>Cmd+Shift+B</kbd> you can run `npm run watch`. The build is done when you see something like this:
To build Void from the terminal instead of from inside VSCode, follow the steps above, but instead of pressing <kbd>Cmd+Shift+B</kbd>, run `npm run watch`. The build is done when you see something like this:
```
[watch-extensions] [00:37:39] Finished compilation extensions with 0 errors after 19303 ms
@ -80,15 +81,17 @@ Alternatively, if you want to build Void from the terminal, instead of pressing
```
#### Common Fixes
- Make sure you followed the prerequisite steps.
- Make sure you followed the prerequisite steps above.
- Make sure you have Node version `20.18.2` (the version in `.nvmrc`)!
- If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`.
- If you get an error with React, try running `NODE_OPTIONS="--max-old-space-size=8192" npm run buildreact`.
- If you see missing styles, wait a few seconds and then reload.
- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
- If you get errors like `npm error libtool: error: unrecognised option: '-static'`, make sure you have GNU libtool instead of BSD libtool (BSD is the default in macos)
- If you get errors like `npm error libtool: error: unrecognised option: '-static'`, when running ./scripts/code.sh, make sure you have GNU libtool instead of BSD libtool (BSD is the default in macos)
- If you get erorrs like `The SUID sandbox helper binary was found, but is not configured correctly` when running ./scripts/code.sh, run
`sudo chown root:root .build/electron/chrome-sandbox && sudo chmod 4755 .build/electron/chrome-sandbox` and then run `./scripts/code.sh` again.
- If you have any other questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
## Packaging

146
package-lock.json generated
View file

@ -42,6 +42,7 @@
"@xterm/addon-webgl": "^0.19.0-beta.98",
"@xterm/headless": "^5.6.0-beta.98",
"@xterm/xterm": "^5.6.0-beta.98",
"ajv": "^8.17.1",
"cross-spawn": "^7.0.6",
"diff": "^7.0.0",
"eslint-plugin-react": "^7.37.4",
@ -1686,6 +1687,30 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/@eslint/js": {
"version": "9.25.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz",
@ -5477,16 +5502,15 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
@ -5511,30 +5535,6 @@
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
@ -9272,6 +9272,23 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -9285,6 +9302,13 @@
"node": ">=10.13.0"
}
},
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
@ -9743,7 +9767,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-fifo": {
@ -9788,7 +9811,6 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
"dev": true,
"funding": [
{
"type": "github",
@ -9898,6 +9920,23 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/file-loader/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/file-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@ -9908,6 +9947,13 @@
"ajv": "^6.9.1"
}
},
"node_modules/file-loader/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/file-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@ -14925,10 +14971,9 @@
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
@ -20618,7 +20663,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -21109,30 +21153,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/schema-utils/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/schema-utils/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/scope-tailwind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.9.tgz",

View file

@ -103,6 +103,7 @@
"@xterm/addon-webgl": "^0.19.0-beta.98",
"@xterm/headless": "^5.6.0-beta.98",
"@xterm/xterm": "^5.6.0-beta.98",
"ajv": "^8.17.1",
"cross-spawn": "^7.0.6",
"diff": "^7.0.0",
"eslint-plugin-react": "^7.37.4",

View file

@ -5,9 +5,6 @@
import React, { JSX, useMemo, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import katex from 'katex'
import 'katex/dist/katex.min.css'
import dompurify from '../../../../../../../base/browser/dompurify/dompurify.js'
import { convertToVscodeLang, detectLanguage } from '../../../../common/helpers/languageHelpers.js'
import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js'
@ -36,59 +33,59 @@ function isValidUri(s: string): boolean {
// renders contiguous string of latex eg $e^{i\pi}$
const LatexRender = ({ latex }: { latex: string }) => {
return <span className="katex-error text-red-500">{latex}</span>
// try {
// let formula = latex;
// let displayMode = false;
try {
let formula = latex;
let displayMode = false;
// // Extract the formula from delimiters
// if (latex.startsWith('$') && latex.endsWith('$')) {
// // Check if it's display math $$...$$
// if (latex.startsWith('$$') && latex.endsWith('$$')) {
// formula = latex.slice(2, -2);
// displayMode = true;
// } else {
// formula = latex.slice(1, -1);
// }
// } else if (latex.startsWith('\\(') && latex.endsWith('\\)')) {
// formula = latex.slice(2, -2);
// } else if (latex.startsWith('\\[') && latex.endsWith('\\]')) {
// formula = latex.slice(2, -2);
// displayMode = true;
// }
// Extract the formula from delimiters
if (latex.startsWith('$') && latex.endsWith('$')) {
// Check if it's display math $$...$$
if (latex.startsWith('$$') && latex.endsWith('$$')) {
formula = latex.slice(2, -2);
displayMode = true;
} else {
formula = latex.slice(1, -1);
}
} else if (latex.startsWith('\\(') && latex.endsWith('\\)')) {
formula = latex.slice(2, -2);
} else if (latex.startsWith('\\[') && latex.endsWith('\\]')) {
formula = latex.slice(2, -2);
displayMode = true;
}
// // Render LaTeX
// const html = katex.renderToString(formula, {
// displayMode: displayMode,
// throwOnError: false,
// output: 'html'
// });
// Render LaTeX
const html = katex.renderToString(formula, {
displayMode: displayMode,
throwOnError: false,
output: 'html'
});
// // Sanitize the HTML output with DOMPurify
// const sanitizedHtml = dompurify.sanitize(html, {
// RETURN_TRUSTED_TYPE: true,
// USE_PROFILES: { html: true, svg: true, mathMl: true }
// });
// Sanitize the HTML output with DOMPurify
const sanitizedHtml = dompurify.sanitize(html, {
RETURN_TRUSTED_TYPE: true,
USE_PROFILES: { html: true, svg: true, mathMl: true }
});
// // Add proper styling based on mode
// const className = displayMode
// ? 'katex-block my-2 text-center'
// : 'katex-inline';
// Add proper styling based on mode
const className = displayMode
? 'katex-block my-2 text-center'
: 'katex-inline';
// // Use the ref approach to avoid dangerouslySetInnerHTML
// const mathRef = React.useRef<HTMLSpanElement>(null);
// Use the ref approach to avoid dangerouslySetInnerHTML
const mathRef = React.useRef<HTMLSpanElement>(null);
// React.useEffect(() => {
// if (mathRef.current) {
// mathRef.current.innerHTML = sanitizedHtml as unknown as string;
// }
// }, [sanitizedHtml]);
React.useEffect(() => {
if (mathRef.current) {
mathRef.current.innerHTML = sanitizedHtml as unknown as string;
}
}, [sanitizedHtml]);
return <span ref={mathRef} className={className}></span>;
} catch (error) {
console.error('KaTeX rendering error:', error);
return <span className="katex-error text-red-500">{latex}</span>;
}
// return <span ref={mathRef} className={className}></span>;
// } catch (error) {
// console.error('KaTeX rendering error:', error);
// return <span className="katex-error text-red-500">{latex}</span>;
// }
}
const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => {

View file

@ -30,6 +30,7 @@ import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName, toolNames }
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
import ErrorBoundary from './ErrorBoundary.js';
import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js';
import { terminalNameOfId } from '../../../terminalToolService.js';
@ -696,6 +697,7 @@ type ToolHeaderParams = {
children?: React.ReactNode;
bottomChildren?: React.ReactNode;
onClick?: () => void;
desc2OnClick?: () => void;
isOpen?: boolean;
className?: string;
}
@ -713,6 +715,7 @@ const ToolHeaderWrapper = ({
bottomChildren,
isError,
onClick,
desc2OnClick,
isOpen,
isRejected,
className, // applies to the main content
@ -781,7 +784,7 @@ const ToolHeaderWrapper = ({
data-tooltip-content={'Canceled'}
data-tooltip-place='top'
/>}
{desc2 && <span className="text-void-fg-4 text-xs">
{desc2 && <span className="text-void-fg-4 text-xs" onClick={desc2OnClick}>
{desc2}
</span>}
{numResults !== undefined && (
@ -1416,7 +1419,7 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
const toolParams = _toolParams as ToolCallParams['run_command']
return {
desc1: `"${toolParams.command}"`,
desc1Info: toolParams.bgTerminalId
desc1Info: toolParams.persistentTerminalId
}
},
'open_persistent_terminal': () => {
@ -1425,7 +1428,7 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
},
'kill_persistent_terminal': () => {
const toolParams = _toolParams as ToolCallParams['kill_persistent_terminal']
return { desc1: toolParams.terminalId }
return { desc1: toolParams.persistentTerminalId }
},
'get_dir_tree': () => {
const toolParams = _toolParams as ToolCallParams['get_dir_tree']
@ -2081,6 +2084,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const terminalToolsService = accessor.get('ITerminalToolService')
const toolsService = accessor.get('IToolsService')
const isError = toolMessage.type === 'tool_error'
const title = getTitle(toolMessage)
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
@ -2090,19 +2094,21 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const { rawParams, params } = toolMessage
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
const { command, persistentTerminalId } = params
if (persistentTerminalId) {
componentParams.desc2 = terminalNameOfId(persistentTerminalId)
componentParams.desc2OnClick = () => terminalToolsService.focusTerminal(persistentTerminalId)
}
if (toolMessage.type === 'success') {
const { result } = toolMessage
const { command } = params
const { resolveReason, result: terminalResult } = result
// it's unclear that this is a button and not an icon.
// componentParams.desc2 = <JumpToTerminalButton
// onClick={() => { terminalToolsService.openTerminal(terminalId) }}
// />
const additionalDetailsStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null)
: resolveReason.type === 'timeout' ? `\n(timed out)`
: null
const msg = toolsService.stringOfResult['run_command'](params, result)
componentParams.children = <ToolChildrenWrapper className='whitespace-pre text-nowrap overflow-auto text-sm'>
<div className='!select-text cursor-auto'>
@ -2110,25 +2116,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
<span className="text-void-fg-1 font-sans">{`Ran command: `}</span>
<span className="font-mono">{command}</span>
</div>
{(terminalResult + additionalDetailsStr).length && <div>
{msg.length && <div>
<span className='text-void-fg-1'>{`Result: `}</span>
<span className="font-mono">{terminalResult}</span>
<span className="font-mono">{additionalDetailsStr}</span>
<span className="font-mono">{msg}</span>
</div>}
</div>
</ToolChildrenWrapper>
if (params.bgTerminalId)
componentParams.desc2 = `(terminal ${params.bgTerminalId})`
}
else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_error' || toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') {
const { bgTerminalId, command } = params
if (bgTerminalId) {
componentParams.desc2 = '(persistent terminal)'
if (terminalToolsService.terminalExists(bgTerminalId))
componentParams.onClick = () => terminalToolsService.focusTerminal(bgTerminalId)
}
if (toolMessage.type === 'tool_error') {
const { result } = toolMessage
componentParams.children = <ToolChildrenWrapper>{result}</ToolChildrenWrapper>
@ -2158,12 +2153,9 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
if (toolMessage.type === 'success') {
const { result } = toolMessage
const { terminalId } = result
if (terminalId) {
componentParams.desc2 = `(terminal ${terminalId})`
if (terminalToolsService.terminalExists(terminalId))
componentParams.onClick = () => terminalToolsService.focusTerminal(terminalId)
}
const { persistentTerminalId } = result
componentParams.desc1 = terminalNameOfId(persistentTerminalId)
componentParams.onClick = () => terminalToolsService.focusTerminal(persistentTerminalId)
}
else if (toolMessage.type === 'tool_error') {
const { result } = toolMessage
@ -2181,6 +2173,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
resultWrapper: ({ toolMessage }) => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const terminalToolsService = accessor.get('ITerminalToolService')
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
const title = getTitle(toolMessage)
@ -2195,7 +2188,9 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
if (toolMessage.type === 'success') {
const { result } = toolMessage
const { persistentTerminalId } = params
componentParams.desc1 = terminalNameOfId(persistentTerminalId)
componentParams.onClick = () => terminalToolsService.focusTerminal(persistentTerminalId)
}
else if (toolMessage.type === 'tool_error') {
const { result } = toolMessage

View file

@ -726,6 +726,7 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
rows={1}
placeholder={placeholder}
/>
{/* <div>{`idx ${optionIdx}`}</div> */}
{isMenuOpen && (
<div
ref={refs.setFloating}

View file

@ -12,6 +12,7 @@ import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
import { AddModelInputBox, AnimatedCheckmarkButton, OllamaSetupInstructions, OneClickSwitchButton, SettingsForProvider } from '../void-settings-tsx/Settings.js';
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js';
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js';
import { isLinux } from '../../../../../../../base/common/platform.js';
const OVERRIDE_VALUE = false
@ -603,7 +604,7 @@ const VoidOnboardingContent = () => {
{/* Slice of Void image */}
<div className='max-w-md w-full h-[30vh] mx-auto flex items-center justify-center'>
<VoidIcon />
{!isLinux && <VoidIcon />}
</div>

View file

@ -9,7 +9,7 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { TerminalExitReason, TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
import { ITerminalService, ITerminalInstance } from '../../../../workbench/contrib/terminal/browser/terminal.js';
import { MAX_TERMINAL_CHARS, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js';
import { MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_CHARS, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js';
import { TerminalResolveReason } from '../common/toolsServiceTypes.js';
@ -39,11 +39,11 @@ function isCommandComplete(output: string) {
}
const nameOfId = (id: string) => {
export const terminalNameOfId = (id: string) => {
if (id === '1') return 'Void Agent'
return `Void Agent (${id})`
}
const idOfName = (name: string) => {
export const idOfTerminalName = (name: string) => {
if (name === 'Void Agent') return '1'
const match = name.match(/Void Agent \((\d+)\)/)
@ -66,7 +66,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
const initializeTerminal = (terminal: ITerminalInstance) => {
// when exit, remove
const d = terminal.onExit(() => {
const terminalId = idOfName(terminal.title)
const terminalId = idOfTerminalName(terminal.title)
if (terminalId !== null && (terminalId in this.terminalInstanceOfId)) delete this.terminalInstanceOfId[terminalId]
d.dispose()
})
@ -75,7 +75,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
// initialize any terminals that are already open
for (const terminal of terminalService.instances) {
const proposedTerminalId = idOfName(terminal.title)
const proposedTerminalId = idOfTerminalName(terminal.title)
if (proposedTerminalId) this.terminalInstanceOfId[proposedTerminalId] = terminal
initializeTerminal(terminal)
@ -111,7 +111,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
const terminalId = this.getValidNewTerminalId();
const terminal = await this.terminalService.createTerminal({
location: TerminalLocation.Panel,
config: { name: nameOfId(terminalId), title: nameOfId(terminalId) },
config: { name: terminalNameOfId(terminalId), title: terminalNameOfId(terminalId) },
})
@ -207,26 +207,34 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
// send the command here
await terminal.sendText(command, true)
// inactivity-based timeout
const waitUntilInactive = new Promise<void>(res => {
let globalTimeoutId: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(globalTimeoutId);
globalTimeoutId = setTimeout(() => {
if (resolveReason) return
const waitUntilInterrupt = isBG ?
// timeout after X seconds
new Promise<void>((res) => {
setTimeout(() => {
resolveReason = { type: 'timeout' };
res();
}, MAX_TERMINAL_INACTIVE_TIME * 1000);
};
res()
}, MAX_TERMINAL_BG_COMMAND_TIME * 1000)
})
// inactivity-based timeout
: new Promise<void>(res => {
let globalTimeoutId: ReturnType<typeof setTimeout>;
const resetTimer = () => {
clearTimeout(globalTimeoutId);
globalTimeoutId = setTimeout(() => {
if (resolveReason) return
const dTimeout = terminal.onData(() => { resetTimer(); });
disposables.push(dTimeout, toDisposable(() => clearTimeout(globalTimeoutId)));
resetTimer();
});
resolveReason = { type: 'timeout' };
res();
}, MAX_TERMINAL_INACTIVE_TIME * 1000);
};
const dTimeout = terminal.onData(() => { resetTimer(); });
disposables.push(dTimeout, toDisposable(() => clearTimeout(globalTimeoutId)));
resetTimer();
})
// wait for result
await Promise.any([waitUntilDone, waitUntilInactive,])
await Promise.any([waitUntilDone, waitUntilInterrupt])
disposables.forEach(d => d.dispose())
if (!isBG) {

View file

@ -16,7 +16,7 @@ import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree
import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'
import { timeout } from '../../../../base/common/async.js'
import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js'
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js'
import { IVoidSettingsService } from '../common/voidSettingsService.js'
@ -93,7 +93,7 @@ const validateRecursiveParamStr = (paramsUnknown: unknown) => {
}
const validateProposedTerminalId = (terminalIdUnknown: unknown) => {
if (!terminalIdUnknown) return '1'
if (!terminalIdUnknown) throw new Error(`A value for terminalID must be specified, but the value was "${terminalIdUnknown}"`)
const terminalId = terminalIdUnknown + ''
return terminalId
}
@ -259,19 +259,19 @@ export class ToolsService implements IToolsService {
// ---
run_command: (params: RawToolParamsObj) => {
const { command: commandUnknown, terminal_id: terminalIdUnknown } = params;
const { command: commandUnknown, persistent_terminal_id: terminalIdUnknown } = params;
const command = validateStr('command', commandUnknown);
const proposedTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null;
return { command, bgTerminalId: proposedTerminalId };
const persistentTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null;
return { command, persistentTerminalId };
},
open_persistent_terminal: (_params: RawToolParamsObj) => {
// No parameters needed; will open a new background terminal
return {};
},
kill_persistent_terminal: (params: RawToolParamsObj) => {
const { terminal_id: terminalIdUnknown } = params;
const terminalId = validateProposedTerminalId(terminalIdUnknown);
return { terminalId };
const { persistent_terminal_id: terminalIdUnknown } = params;
const persistentTerminalId = validateProposedTerminalId(terminalIdUnknown);
return { persistentTerminalId };
},
}
@ -425,21 +425,20 @@ export class ToolsService implements IToolsService {
return { result: lintErrorsPromise }
},
// ---
run_command: async ({ command, bgTerminalId }) => {
const { terminalId, resPromise } = await this.terminalToolService.runCommand(command, bgTerminalId)
run_command: async ({ command, persistentTerminalId }) => {
const { terminalId, resPromise } = await this.terminalToolService.runCommand(command, persistentTerminalId)
const interruptTool = () => {
this.terminalToolService.killTerminal(terminalId)
}
return { result: resPromise, interruptTool }
},
open_persistent_terminal: async () => {
// Open a new background terminal without waiting for completion
const terminalId = await this.terminalToolService.createTerminal()
return { result: { terminalId } }
const persistentTerminalId = await this.terminalToolService.createTerminal()
return { result: { persistentTerminalId } }
},
kill_persistent_terminal: async ({ terminalId }) => {
kill_persistent_terminal: async ({ persistentTerminalId }) => {
// Close the background terminal by sending exit
await this.terminalToolService.killTerminal(terminalId)
await this.terminalToolService.killTerminal(persistentTerminalId)
return { result: {} }
},
@ -517,18 +516,18 @@ export class ToolsService implements IToolsService {
resolveReason,
result: result_,
} = result
const { bgTerminalId } = params
const { persistentTerminalId } = params
// success
if (resolveReason.type === 'done') {
const desc = bgTerminalId ? ` in terminal ${bgTerminalId}` : ''
const desc = persistentTerminalId ? ` in terminal ${persistentTerminalId}` : ''
return `Terminal command executed and finished${desc}. Result (exit code ${resolveReason.exitCode}):\n${result_}`
}
// bg command
if (bgTerminalId !== null) {
if (persistentTerminalId !== null) {
if (resolveReason.type === 'timeout') {
return `Terminal command is running in the background in terminal ${bgTerminalId}. Here were the outputs after ${MAX_TERMINAL_INACTIVE_TIME} seconds:\n${result_}`
return `Terminal command is running in terminal ${persistentTerminalId}. Here are the current outputs (after ${MAX_TERMINAL_BG_COMMAND_TIME} seconds):\n${result_}`
}
}
// normal command
@ -541,11 +540,11 @@ export class ToolsService implements IToolsService {
throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`)
},
open_persistent_terminal: (_params, result) => {
const { terminalId } = result;
return `Successfully created background terminal with ID ${terminalId}`;
const { persistentTerminalId } = result;
return `Successfully created persistent terminal. persistentTerminalId="${persistentTerminalId}"`;
},
kill_persistent_terminal: (params, _result) => {
return `Successfully closed terminal ${params.terminalId}.`;
return `Successfully closed terminal "${params.persistentTerminalId}".`;
},
}

View file

@ -27,6 +27,7 @@ export const MAX_CHILDREN_URIs_PAGE = 500
// terminal tool info
export const MAX_TERMINAL_CHARS = 100_000
export const MAX_TERMINAL_INACTIVE_TIME = 8 // seconds
export const MAX_TERMINAL_BG_COMMAND_TIME = 5
// Maximum character limits for prefix and suffix context
@ -133,12 +134,12 @@ const changesExampleContent = `\
// {{change 3}}
// ... existing code ...`
// const editToolDescriptionExample = `\
// ${tripleTick[0]}
// ${changesExampleContent}
// ${tripleTick[1]}`
const editToolDescriptionExample = `\
${tripleTick[0]}
${changesExampleContent}
${tripleTick[1]}`
const fileNameEditExample = `${tripleTick[0]}typescript
const chatSuggestionDiffExample = `${tripleTick[0]}typescript
/Users/username/Dekstop/my_project/app.ts
${changesExampleContent}
${tripleTick[1]}`
@ -180,6 +181,13 @@ const paginationParam = {
// [K in keyof T as SnakeCase<Extract<K, string>>]: T[K]
// };
const applyToolDescription = (type: 'edit tool' | 'chat suggestion') => `\
${type === 'edit tool' ? 'A' : 'a'} code diff describing the change to make to the file. \
Your DIFF is the only context that will be given to another LLM to apply the change, so it must be accurate and complete. \
Your DIFF MUST be wrapped in triple backticks. \
NEVER re-write the whole file. Always bias towards writing as little as possible. \
Use comments like "// ... existing code ..." to condense your writing. \
Here's an example of a good output:\n${type === 'edit tool' ? editToolDescriptionExample : chatSuggestionDiffExample}`
export const voidTools = {
@ -313,7 +321,7 @@ export const voidTools = {
description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). You can use this tool to run any command: sed, grep, etc. Do not edit any files with this tool; use edit_file instead. When working with git and other tools that open an editor (e.g. git diff), you should pipe to cat to get all results and not get stuck in vim.`,
params: {
command: { description: 'The terminal command to run.' },
bg_terminal_id: { description: 'Optional. This only applies to terminals that have been opened with open_persistent_terminal. Runs the command in the terminal with the specified ID.' },
persistent_terminal_id: { description: 'Optional. Runs the command in the persistent terminal that you created with open_persistent_terminal.' },
},
},
@ -324,8 +332,8 @@ export const voidTools = {
},
kill_persistent_terminal: {
name: 'kill_persistent_terminal',
description: `Closes a BG terminal with the given ID.`,
params: { terminal_id: { description: `The terminal ID to interrupt and close.` } }
description: `Interrupts and closes a persistent terminal that you opened with open_persistent_terminal.`,
params: { persistent_terminal_id: { description: `The ID of the persistent terminal.` } }
}
@ -421,14 +429,14 @@ Please assist the user with their query.`)
<system_info>
- ${os}
- Open workspaces:
${workspaceFolders.join('\n') || 'NO WORKSPACE OPEN'}
- The user's workspace contains these folders:
${workspaceFolders.join('\n') || 'NO FOLDERS OPEN'}
- Active file:
${activeURI}
- Open files:
${openedURIs.join('\n') || 'NO OPENED EDITORS'}${''/* separator */}${mode === 'agent' && persistentTerminalIDs.length !== 0 ? `
${openedURIs.join('\n') || 'NO OPENED FILES'}${''/* separator */}${mode === 'agent' && persistentTerminalIDs.length !== 0 ? `
- Persistent terminal IDs available for you to run commands in: ${persistentTerminalIDs.join(', ')}` : ''}
</system_info>`)
@ -460,7 +468,7 @@ ${directoryStr}
details.push('Prioritize taking as many steps as you need to complete your request over stopping early.')
details.push(`You will OFTEN need to gather context before making a change. Do not immediately make a change unless you have ALL relevant context.`)
details.push(`ALWAYS have maximal certainty in a change BEFORE you make it. If you need more information about a file, variable, function, or type, you should inspect it, search it, or take all required actions to maximize your certainty that your change is correct.`)
details.push(`NEVER modify a file outside the user's workspace(s) without permission from the user.`)
details.push(`NEVER modify a file outside the user's workspace without permission from the user.`)
}
if (mode === 'gather') {
@ -475,12 +483,8 @@ ${directoryStr}
- The remaining contents of the file should proceed as usual.`)
details.push(`If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion in CODE BLOCK(S).
- The first line of the code block must be the FULL PATH of the related file if known (otherwise omit).
- The remaining contents should be \
a brief code description of the change you want to make, with comments like "// ... existing code ..." to condense your writing. \
NEVER re-write the whole file. Instead, use comments like "// ... existing code ...". Bias towards writing as little as possible. \
Here's an example of a good edit suggestion:
${fileNameEditExample}.`)
- The first line of the code block must be the FULL PATH of the related file.
- The remaining contents should be ${applyToolDescription('chat suggestion')}`)
}
details.push(`NEVER write the FULL PATH of a file when speaking with the user. Just write the file name ONLY.`)

View file

@ -48,9 +48,9 @@ export type ToolCallParams = {
'create_file_or_folder': { uri: URI, isFolder: boolean },
'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean },
// ---
'run_command': { command: string; bgTerminalId: string | null },
'run_command': { command: string; persistentTerminalId: string | null },
'open_persistent_terminal': {},
'kill_persistent_terminal': { terminalId: string },
'kill_persistent_terminal': { persistentTerminalId: string },
}
// RESULT OF TOOL CALL
@ -69,7 +69,7 @@ export type ToolResultType = {
'delete_file_or_folder': {},
// ---
'run_command': { result: string; resolveReason: TerminalResolveReason; },
'open_persistent_terminal': { terminalId: string },
'open_persistent_terminal': { persistentTerminalId: string },
'kill_persistent_terminal': {},
}