mirror of
https://github.com/voideditor/void
synced 2026-05-23 01:18:25 +00:00
finish rebase from voideditor-test/void
This commit is contained in:
parent
d15e559a73
commit
19dda35ff7
40 changed files with 1272 additions and 1060 deletions
|
|
@ -4,15 +4,15 @@ This is the official guide on how to contribute to Void. We want to make it as e
|
|||
|
||||
There are a few ways to contribute:
|
||||
|
||||
- 💫 Complete items on the [Roadmap](https://github.com/orgs/voideditor/projects/2).
|
||||
- 💫 Complete items on the [Roadmap](https://github.com/orgs/voideditor-test/projects/2).
|
||||
- 💡 Make suggestions in our [Discord](https://discord.gg/RSNjgaugJs).
|
||||
- 🪴 Start new Issues - see [Issues](https://github.com/voideditor/void/issues).
|
||||
- 🪴 Start new Issues - see [Issues](https://github.com/voideditor-test/void/issues).
|
||||
|
||||
|
||||
|
||||
### Codebase Guide
|
||||
|
||||
We [highly recommend reading this](https://github.com/voideditor/void/blob/main/VOID_CODEBASE_GUIDE.md) guide that we put together on Void's sourcecode if you'd like to contribute!
|
||||
We [highly recommend reading this](https://github.com/voideditor-test/void/blob/main/VOID_CODEBASE_GUIDE.md) guide that we put together on Void's sourcecode if you'd like to contribute!
|
||||
|
||||
The repo is not as intimidating as it first seems if you read the guide!
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ Alternatively, if you want to build Void from the terminal, instead of pressing
|
|||
- 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 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 have any questions, feel free to [submit an issue](https://github.com/voideditor-test/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)
|
||||
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ workspace/
|
|||
```
|
||||
|
||||
### Distributing
|
||||
Void's maintainers distribute Void on our website and in releases. Our build pipeline is a fork of VSCodium, and it works by running GitHub Actions which create the downloadables. The build repo with more instructions lives [here](https://github.com/voideditor/void-builder).
|
||||
Void's maintainers distribute Void on our website and in releases. Our build pipeline is a fork of VSCodium, and it works by running GitHub Actions which create the downloadables. The build repo with more instructions lives [here](https://github.com/voideditor-test/void-builder).
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -11,30 +11,30 @@
|
|||
|
||||
Void is the open-source Cursor alternative.
|
||||
|
||||
This repo contains the full sourcecode for Void. We are currently in [open beta](https://voideditor.com/email) for Discord members (see the `announcements` channel), with a waitlist for our official release. If you're new, welcome!
|
||||
This repo contains the full sourcecode for Void. We are currently in [open beta](https://voideditor-test.com/email) for Discord members (see the `announcements` channel), with a waitlist for our official release. If you're new, welcome!
|
||||
|
||||
- 👋 [Discord](https://discord.gg/RSNjgaugJs)
|
||||
|
||||
- 🔨 [Contribute](https://github.com/voideditor/void/blob/main/HOW_TO_CONTRIBUTE.md)
|
||||
- 🔨 [Contribute](https://github.com/voideditor-test/void/blob/main/HOW_TO_CONTRIBUTE.md)
|
||||
|
||||
- 🚙 [Roadmap](https://github.com/orgs/voideditor/projects/2)
|
||||
- 🚙 [Roadmap](https://github.com/orgs/voideditor-test/projects/2)
|
||||
|
||||
- 📝 [Changelog](https://voideditor.com/changelog)
|
||||
- 📝 [Changelog](https://voideditor-test.com/changelog)
|
||||
|
||||
- 🧭 [Codebase Guide](https://github.com/voideditor/void/blob/main/VOID_CODEBASE_GUIDE.md)
|
||||
- 🧭 [Codebase Guide](https://github.com/voideditor-test/void/blob/main/VOID_CODEBASE_GUIDE.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Feel free to attend a weekly meeting in our Discord channel if you'd like to contribute!
|
||||
|
||||
2. To get started working on Void, see [`HOW_TO_CONTRIBUTE`](https://github.com/voideditor/void/blob/main/HOW_TO_CONTRIBUTE.md).
|
||||
2. To get started working on Void, see [`HOW_TO_CONTRIBUTE`](https://github.com/voideditor-test/void/blob/main/HOW_TO_CONTRIBUTE.md).
|
||||
|
||||
3. We're open to collaborations and suggestions of all types - just reach out.
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
Void is a fork of the [vscode](https://github.com/microsoft/vscode) repository. For a guide to the VSCode/Void codebase, see [`VOID_CODEBASE_GUIDE`](https://github.com/voideditor/void/blob/main/VOID_CODEBASE_GUIDE.md).
|
||||
Void is a fork of the [vscode](https://github.com/microsoft/vscode) repository. For a guide to the VSCode/Void codebase, see [`VOID_CODEBASE_GUIDE`](https://github.com/voideditor-test/void/blob/main/VOID_CODEBASE_GUIDE.md).
|
||||
|
||||
## Support
|
||||
Feel free to reach out in our Discord or contact us via email: hello@voideditor.com.
|
||||
Feel free to reach out in our Discord or contact us via email: hello@voideditor-test.com.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ The Void codebase is not as intimidating as it seems!
|
|||
|
||||
Most of Void's code lives in the folder `src/vs/workbench/contrib/void/`.
|
||||
|
||||
The purpose of this document is to explain how Void's codebase works. If you want build instructions instead, see [Contributing](https://github.com/voideditor/void/blob/main/HOW_TO_CONTRIBUTE.md).
|
||||
The purpose of this document is to explain how Void's codebase works. If you want build instructions instead, see [Contributing](https://github.com/voideditor-test/void/blob/main/HOW_TO_CONTRIBUTE.md).
|
||||
|
||||
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ Sending LLM messages from the main process avoids CSP issues with local provider
|
|||
|
||||
|
||||
|
||||
**Notes:** `modelCapabilities` is an important file that must be updated when new models come out!
|
||||
**Notes:** `modelCapabilities` is an important file that must be updated when new models come out!
|
||||
|
||||
|
||||
### Apply
|
||||
|
|
@ -74,16 +74,16 @@ Void has two types of Apply: **Fast Apply** (uses Search/Replace, see below), an
|
|||
When you click Apply and Fast Apply is enabled, we prompt the LLM to output Search/Replace block(s) like this:
|
||||
```
|
||||
<<<<<<< ORIGINAL
|
||||
// original code goes here
|
||||
// original code goes here
|
||||
=======
|
||||
// replaced code goes here
|
||||
>>>>>>> UPDATED
|
||||
```
|
||||
This is what allows Void to quickly apply code even on 1000-line files. It's the same as asking the LLM to press Ctrl+F and enter in a search/replace query.
|
||||
This is what allows Void to quickly apply code even on 1000-line files. It's the same as asking the LLM to press Ctrl+F and enter in a search/replace query.
|
||||
|
||||
### Apply Inner Workings
|
||||
|
||||
The `editCodeService` file runs Apply. The same exact code is also used when the LLM calls the Edit tool, and when you submit Cmd+K. Just different versions of Fast/Slow Apply mode.
|
||||
The `editCodeService` file runs Apply. The same exact code is also used when the LLM calls the Edit tool, and when you submit Cmd+K. Just different versions of Fast/Slow Apply mode.
|
||||
|
||||
Here is some important terminology:
|
||||
- A **DiffZone** is a {startLine, endLine} region of text where we compute and show red/green areas, or **Diffs**. When any changes are made to a file, we loop through all the DiffAreas on that file and refresh its Diffs.
|
||||
|
|
@ -110,7 +110,7 @@ Here's a guide to some of the terminology we're using:
|
|||
- **FeatureName**: Autocomplete | Chat | CtrlK | Apply
|
||||
- **ModelSelection**: a {providerName, modelName} pair.
|
||||
- **ProviderName**: The name of a provider: `'ollama'`, `'openAI'`, etc.
|
||||
- **ModelName**: The name of a model (string type, eg `'gpt-4o'`).
|
||||
- **ModelName**: The name of a model (string type, eg `'gpt-4o'`).
|
||||
- **RefreshProvider**: a provider that we ping repeatedly to update the models list.
|
||||
- **ChatMode** = normal | gather | agent
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ Here's a guide to some of the terminology we're using:
|
|||
|
||||
|
||||
### Build process
|
||||
If you want to know how our build pipeline works, see our build repo [here](https://github.com/voideditor/void-builder).
|
||||
If you want to know how our build pipeline works, see our build repo [here](https://github.com/voideditor-test/void-builder).
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ function buildWin32Setup(arch, target) {
|
|||
productJson['target'] = target;
|
||||
fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t'));
|
||||
|
||||
console.log('RawVersion!!!!!!!!!!!!!!', pkg.version.replace(/-\w+$/, '')) // Void
|
||||
const quality = product.quality || 'dev';
|
||||
const definitions = {
|
||||
NameLong: product.nameLong,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "open-remote-ssh",
|
||||
"displayName": "Open Remote - SSH",
|
||||
"publisher": "voideditor",
|
||||
"publisher": "voideditor-test",
|
||||
"description": "Use any remote machine with a SSH server as your development environment.",
|
||||
"version": "0.0.48",
|
||||
"icon": "resources/icon.png",
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
"type": "string",
|
||||
"description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number",
|
||||
"scope": "application",
|
||||
"default": "https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
"default": "https://github.com/voideditor-test/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
},
|
||||
"remote.SSH.remotePlatform": {
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -32,14 +32,12 @@ export async function getVSCodeServerConfig(): Promise<IServerConfig> {
|
|||
const customServerBinaryName = vscode.workspace.getConfiguration('remote.SSH.experimental').get<string>('serverBinaryName', '');
|
||||
|
||||
return {
|
||||
// version: vscode.version.replace('-insider', ''),
|
||||
version: vscode.version.replace('-insider', ''),
|
||||
commit: productJson.commit,
|
||||
quality: productJson.quality,
|
||||
release: productJson.release,
|
||||
serverApplicationName: customServerBinaryName || productJson.serverApplicationName,
|
||||
serverDataFolderName: productJson.serverDataFolderName,
|
||||
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate,
|
||||
// Void changed this
|
||||
version: productJson.voidVersion
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class ServerInstallError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz';
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor-test/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz';
|
||||
|
||||
export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log): Promise<ServerInstallResult> {
|
||||
let shell = 'powershell';
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"type": "string",
|
||||
"description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number",
|
||||
"scope": "application",
|
||||
"default": "https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
"default": "https://github.com/voideditor-test/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -31,14 +31,12 @@ export async function getVSCodeServerConfig(): Promise<IServerConfig> {
|
|||
const productJson = await getVSCodeProductJson();
|
||||
|
||||
return {
|
||||
// version: vscode.version.replace('-insider', ''),
|
||||
version: vscode.version.replace('-insider', ''),
|
||||
commit: productJson.commit,
|
||||
quality: productJson.quality,
|
||||
release: productJson.release,
|
||||
serverApplicationName: productJson.serverApplicationName,
|
||||
serverDataFolderName: productJson.serverDataFolderName,
|
||||
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate,
|
||||
// Void changed this
|
||||
version: productJson.voidVersion
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class ServerInstallError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz';
|
||||
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor-test/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz';
|
||||
|
||||
export async function installCodeServer(wslManager: WSLManager, distroName: string, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], logger: Log): Promise<ServerInstallResult> {
|
||||
const scriptId = crypto.randomBytes(12).toString('hex');
|
||||
|
|
|
|||
752
package-lock.json
generated
752
package-lock.json
generated
File diff suppressed because it is too large
Load diff
43
package.json
43
package.json
|
|
@ -72,14 +72,14 @@
|
|||
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@floating-ui/react": "^0.27.5",
|
||||
"@google/generative-ai": "^0.22.0",
|
||||
"@anthropic-ai/sdk": "^0.40.0",
|
||||
"@c4312/eventsource-umd": "^3.0.5",
|
||||
"@floating-ui/react": "^0.27.8",
|
||||
"@google/generative-ai": "^0.24.0",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@mistralai/mistralai": "^1.6.0",
|
||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vscode/deviceid": "^0.1.1",
|
||||
|
|
@ -105,28 +105,28 @@
|
|||
"@xterm/addon-webgl": "^0.19.0-beta.99",
|
||||
"@xterm/headless": "^5.6.0-beta.99",
|
||||
"@xterm/xterm": "^5.6.0-beta.99",
|
||||
"ajv": "^8.17.1",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"diff": "^7.0.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"groq-sdk": "^0.15.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"groq-sdk": "^0.20.1",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"jschardet": "3.1.4",
|
||||
"katex": "^0.16.22",
|
||||
"kerberos": "2.1.1",
|
||||
"lucide-react": "^0.477.0",
|
||||
"marked": "^15.0.7",
|
||||
"lucide-react": "^0.503.0",
|
||||
"marked": "^15.0.11",
|
||||
"minimist": "^1.2.6",
|
||||
"native-is-elevated": "0.7.0",
|
||||
"native-keymap": "^3.3.5",
|
||||
"native-watchdog": "^1.4.1",
|
||||
"node-pty": "^1.1.0-beta33",
|
||||
"ollama": "^0.5.14",
|
||||
"ollama": "^0.5.15",
|
||||
"open": "^8.4.2",
|
||||
"openai": "^4.86.1",
|
||||
"posthog-node": "^4.8.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"openai": "^4.96.0",
|
||||
"posthog-node": "^4.14.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-tooltip": "^5.28.1",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"v8-inspect-profiler": "^0.1.1",
|
||||
|
|
@ -142,17 +142,16 @@
|
|||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/cookie": "^0.3.3",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/gulp-svgmin": "^1.2.1",
|
||||
"@types/http-proxy-agent": "^2.0.1",
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/kerberos": "^1.1.2",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/node": "20.x",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/sinon": "^10.0.2",
|
||||
"@types/sinon-test": "^2.4.2",
|
||||
"@types/trusted-types": "^1.0.6",
|
||||
|
|
@ -222,8 +221,8 @@
|
|||
"mocha": "^10.8.2",
|
||||
"mocha-junit-reporter": "^2.2.1",
|
||||
"mocha-multi-reporters": "^1.5.1",
|
||||
"next": "^15.2.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"next": "^15.3.1",
|
||||
"nodemon": "^3.1.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"original-fs": "^1.2.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
|
|
@ -246,7 +245,7 @@
|
|||
"tsec": "0.2.7",
|
||||
"tslib": "^2.6.3",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript": "^5.8.0-dev.20250207",
|
||||
"typescript-eslint": "^8.8.0",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.94.0",
|
||||
|
|
|
|||
16
product.json
16
product.json
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"nameShort": "Void",
|
||||
"nameLong": "Void",
|
||||
"voidVersion": "1.2.6",
|
||||
"voidVersion": "1.2.8",
|
||||
"applicationName": "void",
|
||||
"dataFolderName": ".void-editor",
|
||||
"win32MutexName": "voideditor",
|
||||
"win32MutexName": "voideditor-test",
|
||||
"licenseName": "MIT",
|
||||
"licenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
|
||||
"serverLicenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
|
||||
"licenseUrl": "https://github.com/voideditor-test/void/blob/main/LICENSE.txt",
|
||||
"serverLicenseUrl": "https://github.com/voideditor-test/void/blob/main/LICENSE.txt",
|
||||
"serverGreeting": [],
|
||||
"serverLicense": [],
|
||||
"serverLicensePrompt": "",
|
||||
|
|
@ -25,10 +25,10 @@
|
|||
"win32ShellNameShort": "V&oid",
|
||||
"win32TunnelServiceMutex": "void-tunnelservice",
|
||||
"win32TunnelMutex": "void-tunnel",
|
||||
"darwinBundleIdentifier": "com.voideditor.code",
|
||||
"darwinBundleIdentifier": "com.voideditor-test.code",
|
||||
"linuxIconName": "void-editor",
|
||||
"licenseFileName": "LICENSE.txt",
|
||||
"reportIssueUrl": "https://github.com/voideditor/void/issues/new",
|
||||
"reportIssueUrl": "https://github.com/voideditor-test/void/issues/new",
|
||||
"nodejsRepository": "https://nodejs.org",
|
||||
"urlProtocol": "void",
|
||||
"extensionsGallery": {
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
"builtInExtensions": [],
|
||||
"linkProtectionTrustedDomains": [
|
||||
"https://voideditor.com",
|
||||
"https://voideditor.dev"
|
||||
"https://voideditor-test.com",
|
||||
"https://voideditor-test.dev"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -57,8 +57,8 @@ export type ExtensionVirtualWorkspaceSupport = {
|
|||
|
||||
export interface IProductConfiguration {
|
||||
readonly version: string;
|
||||
readonly voidVersion: string; // Void added this
|
||||
readonly release: string; // VSCodium added this
|
||||
readonly voidVersion?: string;
|
||||
readonly release?: string; // Void - VSCodium added this, we add it for TS
|
||||
readonly date?: string;
|
||||
readonly quality?: string;
|
||||
readonly commit?: string;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { SuggestController } from '../../../../../editor/contrib/suggest/browser
|
|||
import { localize, localize2 } from '../../../../../nls.js';
|
||||
import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js';
|
||||
import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js';
|
||||
import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
|
||||
import { Action2, MenuId, MenuItemAction, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
|
||||
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
|
||||
|
|
|
|||
|
|
@ -4,43 +4,43 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { coalesce, isNonEmptyArray } from '../../../../base/common/arrays.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
// import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
|
||||
import { Event } from '../../../../base/common/event.js';
|
||||
import { MarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
// import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import * as strings from '../../../../base/common/strings.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
// import { URI } from '../../../../base/common/uri.js';
|
||||
// import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
// import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
|
||||
// import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ExtensionIdentifier, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';
|
||||
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
// import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { IProductService } from '../../../../platform/product/common/productService.js';
|
||||
import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { ViewPane } from '../../../browser/parts/views/viewPane.js';
|
||||
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
|
||||
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js';
|
||||
// import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
// import { ViewPane } from '../../../browser/parts/views/viewPane.js';
|
||||
// import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
|
||||
import { IWorkbenchContribution, } from '../../../common/contributions.js';
|
||||
import { IViewsRegistry, Extensions as ViewExtensions } from '../../../common/views.js';
|
||||
import { Extensions, IExtensionFeaturesRegistry, IExtensionFeatureTableRenderer, IRenderedData, IRowData, ITableData } from '../../../services/extensionManagement/common/extensionFeatures.js';
|
||||
import { isProposedApiEnabled } from '../../../services/extensions/common/extensions.js';
|
||||
import * as extensionsRegistry from '../../../services/extensions/common/extensionsRegistry.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
// import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { showExtensionsWithIdsCommandId } from '../../extensions/browser/extensionsActions.js';
|
||||
import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';
|
||||
import { IChatAgentData, IChatAgentService } from '../common/chatAgents.js';
|
||||
import { ChatContextKeys } from '../common/chatContextKeys.js';
|
||||
import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js';
|
||||
import { IChatService } from '../common/chatService.js';
|
||||
import { ChatAgentLocation, ChatConfiguration } from '../common/constants.js';
|
||||
import { ChatViewId, showChatView } from './chat.js';
|
||||
import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js';
|
||||
// import { IChatService } from '../common/chatService.js';
|
||||
import { ChatAgentLocation } from '../common/constants.js';
|
||||
import { ChatViewId } from './chat.js';
|
||||
// import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js';
|
||||
|
||||
// --- Chat Container & View Registration
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
|||
const allLinebreakSymbols = ['\r\n', '\n']
|
||||
const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1]
|
||||
|
||||
// The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts
|
||||
// The extension this was called from is here - https://github.com/voideditor-test/void/blob/autocomplete/extensions/void/src/extension/extension.ts
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { THREAD_STORAGE_KEY } from '../common/storageKeys.js';
|
|||
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
||||
import { timeout } from '../../../../base/common/async.js';
|
||||
import { deepClone } from '../../../../base/common/objects.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
|
||||
|
||||
// related to retrying when LLM message has error
|
||||
|
|
@ -140,15 +141,39 @@ export type IsRunningType =
|
|||
|
||||
export type ThreadStreamState = {
|
||||
[threadId: string]: undefined | {
|
||||
// state related to streaming (not just when streaming)
|
||||
isRunning?: IsRunningType; // whether or not actually running the agent loop (can be running and not streaming, like if it's calling a tool and awaiting user response)
|
||||
isRunning: undefined;
|
||||
error?: { message: string, fullError: Error | null, };
|
||||
|
||||
// streaming related - when streaming message
|
||||
streamingToken?: string;
|
||||
displayContentSoFar?: string;
|
||||
reasoningSoFar?: string;
|
||||
toolCallSoFar?: RawToolCallObj;
|
||||
llmInfo?: undefined;
|
||||
toolInfo?: undefined;
|
||||
interrupt?: undefined;
|
||||
} | { // an assistant message is being written
|
||||
isRunning: 'LLM';
|
||||
error?: undefined;
|
||||
llmInfo: {
|
||||
displayContentSoFar: string;
|
||||
reasoningSoFar: string;
|
||||
toolCallSoFar: RawToolCallObj | null;
|
||||
};
|
||||
toolInfo?: undefined;
|
||||
interrupt: Promise<() => void>; // calling this should have no effect on state - would be too confusing. it just cancels the tool
|
||||
} | { // a tool is being run
|
||||
isRunning: 'tool';
|
||||
error?: undefined;
|
||||
llmInfo?: undefined;
|
||||
toolInfo: {
|
||||
toolName: ToolName;
|
||||
toolParams: ToolCallParams[ToolName];
|
||||
id: string;
|
||||
content: string;
|
||||
rawParams: RawToolParamsObj;
|
||||
};
|
||||
interrupt: Promise<() => void>;
|
||||
} | {
|
||||
isRunning: 'awaiting_user';
|
||||
error?: undefined;
|
||||
llmInfo?: undefined;
|
||||
toolInfo?: undefined;
|
||||
interrupt?: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,10 +238,11 @@ export interface IChatThreadService {
|
|||
// codespan links (link to symbols in the markdown)
|
||||
getCodespanLink(opts: { codespanStr: string, messageIdx: number, threadId: string }): CodespanLocationLink | undefined;
|
||||
addCodespanLink(opts: { newLinkText: string, newLinkLocation: CodespanLocationLink, messageIdx: number, threadId: string }): void;
|
||||
generateCodespanLink(opts: { codespanStr: string, threadId: string }): Promise<CodespanLocationLink>
|
||||
generateCodespanLink(opts: { codespanStr: string, threadId: string }): Promise<CodespanLocationLink>;
|
||||
getRelativeStr(uri: URI): string | undefined
|
||||
|
||||
// entry pts
|
||||
stopRunning(threadId: string): void;
|
||||
abortRunning(threadId: string): Promise<void>;
|
||||
dismissStreamError(threadId: string): void;
|
||||
|
||||
// call to edit a message
|
||||
|
|
@ -263,6 +289,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
@IEditCodeService private readonly _editCodeService: IEditCodeService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IConvertToLLMMessageService private readonly _convertToLLMMessagesService: IConvertToLLMMessageService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
super()
|
||||
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
|
||||
|
|
@ -341,32 +368,41 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// this should be the only place this.state = ... appears besides constructor
|
||||
private _setState(state: Partial<ThreadsState>, affectsCurrent: boolean) {
|
||||
this.state = {
|
||||
const newState = {
|
||||
...this.state,
|
||||
...state
|
||||
}
|
||||
|
||||
this.state = newState
|
||||
|
||||
if (affectsCurrent)
|
||||
this._onDidChangeCurrentThread.fire()
|
||||
|
||||
|
||||
// if we just switched to a thread, update its current stream state if it's not streaming to possibly streaming
|
||||
const threadId = newState.currentThreadId
|
||||
const streamState = this.streamState[threadId]
|
||||
if (streamState?.isRunning === undefined && !streamState?.error) {
|
||||
|
||||
// set streamState
|
||||
const messages = newState.allThreads[threadId]?.messages
|
||||
const lastMessage = messages && messages[messages.length - 1]
|
||||
// if awaiting user but stream state doesn't indicate it (happens if restart Void)
|
||||
if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'tool_request')
|
||||
this._setStreamState(threadId, { isRunning: 'awaiting_user', })
|
||||
|
||||
// if running now but stream state doesn't indicate it (happens if restart Void), cancel that last tool
|
||||
if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'running_now') {
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', content: lastMessage.content, id: lastMessage.id, rawParams: lastMessage.rawParams, result: null, name: lastMessage.name, params: lastMessage.params })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private _setStreamState(threadId: string, state: Partial<NonNullable<ThreadStreamState[string]>>, behavior: 'set' | 'merge') {
|
||||
if (state === undefined)
|
||||
delete this.streamState[threadId]
|
||||
|
||||
else {
|
||||
if (behavior === 'merge') {
|
||||
this.streamState[threadId] = {
|
||||
...this.streamState[threadId],
|
||||
...state
|
||||
}
|
||||
}
|
||||
else if (behavior === 'set') {
|
||||
this.streamState[threadId] = state
|
||||
}
|
||||
else throw new Error(`setStreamState`)
|
||||
}
|
||||
|
||||
private _setStreamState(threadId: string, state: ThreadStreamState[string]) {
|
||||
this.streamState[threadId] = state
|
||||
this._onDidChangeStreamState.fire({ threadId })
|
||||
}
|
||||
|
||||
|
|
@ -433,40 +469,35 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
const errorMessage = this.errMsgs.rejected
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams })
|
||||
this._setStreamState(threadId, {}, 'set')
|
||||
this._setStreamState(threadId, undefined)
|
||||
}
|
||||
|
||||
stopRunning(threadId: string) {
|
||||
async abortRunning(threadId: string) {
|
||||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return // should never happen
|
||||
|
||||
// reject the tool for the user if relevant
|
||||
this.rejectLatestToolRequest(threadId)
|
||||
|
||||
// interrupt the tool if relevant
|
||||
this._currentlyRunningToolInterruptor[threadId]?.()
|
||||
|
||||
// interrupt assistant message
|
||||
const isRunning = this.streamState[threadId]?.isRunning
|
||||
if (isRunning === 'LLM') {
|
||||
// abort the stream first so it doesn't change any state
|
||||
const displayContentSoFar = this.streamState[threadId]?.displayContentSoFar ?? ''
|
||||
const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? ''
|
||||
const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar
|
||||
|
||||
const llmCancelToken = this.streamState[threadId]?.streamingToken
|
||||
if (llmCancelToken !== undefined) { this._llmMessageService.abort(llmCancelToken) }
|
||||
// interrupt any effects
|
||||
const interrupt = await this.streamState[threadId]?.interrupt
|
||||
interrupt?.()
|
||||
|
||||
// add assistant message
|
||||
if (this.streamState[threadId]?.isRunning === 'LLM') {
|
||||
const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo
|
||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||
|
||||
if (toolCallSoFar) {
|
||||
this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name })
|
||||
}
|
||||
|
||||
if (toolCallSoFar) this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name })
|
||||
this._addUserCheckpoint({ threadId })
|
||||
}
|
||||
// add tool that's running
|
||||
else if (this.streamState[threadId]?.isRunning === 'tool') {
|
||||
const { toolName, toolParams, id, content, rawParams } = this.streamState[threadId].toolInfo
|
||||
this._updateLatestTool(threadId, { role: 'tool', name: toolName, params: toolParams, id, content, rawParams, type: 'rejected', result: null })
|
||||
}
|
||||
// reject the tool for the user if relevant
|
||||
else if (this.streamState[threadId]?.isRunning === 'awaiting_user') {
|
||||
this.rejectLatestToolRequest(threadId)
|
||||
}
|
||||
|
||||
this._setStreamState(threadId, {}, 'set')
|
||||
this._setStreamState(threadId, undefined)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -477,7 +508,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
}
|
||||
|
||||
|
||||
private readonly _currentlyRunningToolInterruptor: { [threadId: string]: (() => void) | undefined } = {}
|
||||
// private readonly _currentlyRunningToolInterruptor: { [threadId: string]: (() => void) | undefined } = {}
|
||||
|
||||
|
||||
// returns true when the tool call is waiting for user approval
|
||||
|
|
@ -496,7 +527,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
if (!opts.preapproved) { // skip this if pre-approved
|
||||
// 1. validate tool params
|
||||
try {
|
||||
const params = await this._toolsService.validateParams[toolName](opts.unvalidatedToolParams)
|
||||
const params = this._toolsService.validateParams[toolName](opts.unvalidatedToolParams)
|
||||
toolParams = params
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error)
|
||||
|
|
@ -526,30 +557,38 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
|
||||
// 3. call the tool
|
||||
this._setStreamState(threadId, { isRunning: 'tool' }, 'merge')
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||
// this._setStreamState(threadId, { isRunning: 'tool' }, 'merge')
|
||||
const runningTool = { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null, id: toolId, rawParams: opts.unvalidatedToolParams } as const
|
||||
this._updateLatestTool(threadId, runningTool)
|
||||
|
||||
|
||||
let interrupted = false
|
||||
let resolveInterruptor: (r: () => void) => void = () => { }
|
||||
const interruptorPromise = new Promise<() => void>(res => { resolveInterruptor = res })
|
||||
try {
|
||||
|
||||
// set stream state
|
||||
this._setStreamState(threadId, { isRunning: 'tool', interrupt: interruptorPromise, toolInfo: { toolName, toolParams, id: toolId, content: 'interrupted...', rawParams: opts.unvalidatedToolParams } })
|
||||
|
||||
const { result, interruptTool } = await this._toolsService.callTool[toolName](toolParams as any)
|
||||
this._currentlyRunningToolInterruptor[threadId] = () => {
|
||||
interrupted = true;
|
||||
interruptTool?.();
|
||||
delete this._currentlyRunningToolInterruptor[threadId];
|
||||
}
|
||||
toolResult = await result // ts is bad... await is needed
|
||||
const interruptor = () => { interrupted = true; interruptTool?.() }
|
||||
resolveInterruptor(interruptor)
|
||||
|
||||
toolResult = await result
|
||||
|
||||
if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here
|
||||
}
|
||||
catch (error) {
|
||||
delete this._currentlyRunningToolInterruptor[threadId]
|
||||
resolveInterruptor(() => { }) // resolve for the sake of it
|
||||
if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here
|
||||
|
||||
|
||||
const errorMessage = getErrorMessage(error)
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||
return {}
|
||||
}
|
||||
finally {
|
||||
this._setStreamState(threadId, undefined)
|
||||
}
|
||||
|
||||
// 4. stringify the result to give to the LLM
|
||||
try {
|
||||
|
|
@ -562,8 +601,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// 5. add to history and keep going
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'success', params: toolParams, result: toolResult, name: toolName, content: toolResultStr, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||
delete this._currentlyRunningToolInterruptor[threadId]
|
||||
|
||||
return {}
|
||||
};
|
||||
|
||||
|
|
@ -587,8 +624,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// above just defines helpers, below starts the actual function
|
||||
const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here
|
||||
|
||||
// clear any previous error
|
||||
this._setStreamState(threadId, { error: undefined }, 'set')
|
||||
// not running at start, clear state
|
||||
this._setStreamState(threadId, undefined)
|
||||
|
||||
let nMessagesSent = 0
|
||||
let shouldSendAnotherMessage = true
|
||||
|
|
@ -597,7 +634,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// before enter loop, call tool
|
||||
if (callThisToolFirst) {
|
||||
const { interrupted } = await this._runToolCall(threadId, callThisToolFirst.name, callThisToolFirst.id, { preapproved: true, unvalidatedToolParams: callThisToolFirst.rawParams, validatedParams: callThisToolFirst.params })
|
||||
if (interrupted) return
|
||||
this._setStreamState(threadId, undefined)
|
||||
if (interrupted) { return }
|
||||
}
|
||||
|
||||
// tool use loop
|
||||
|
|
@ -618,14 +656,16 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
let shouldRetry = true
|
||||
let nAttempts = 0
|
||||
while (shouldRetry) {
|
||||
if (this.streamState[threadId]?.isRunning) {
|
||||
// if already streaming, stop
|
||||
console.log('returning...', this.streamState[threadId])
|
||||
return
|
||||
}
|
||||
shouldRetry = false
|
||||
|
||||
let resMessageIsDonePromise: (toolCall?: RawToolCallObj | undefined) => void // resolves when user approves this tool use (or if tool doesn't require approval)
|
||||
const messageIsDonePromise = new Promise<RawToolCallObj | undefined>((res, rej) => { resMessageIsDonePromise = res })
|
||||
|
||||
// send llm message
|
||||
this._setStreamState(threadId, { isRunning: 'LLM' }, 'merge')
|
||||
|
||||
const llmCancelToken = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'chatMessages',
|
||||
chatMode,
|
||||
|
|
@ -635,84 +675,79 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
logging: { loggingName: `Chat - ${chatMode}`, loggingExtras: { threadId, nMessagesSent, chatMode } },
|
||||
separateSystemMessage: separateSystemMessage,
|
||||
onText: ({ fullText, fullReasoning, toolCall }) => {
|
||||
this._setStreamState(threadId, { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall }, 'merge')
|
||||
this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall ?? null }, interrupt: Promise.resolve(() => { if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) }) })
|
||||
},
|
||||
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||
this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
||||
this._setStreamState(threadId, undefined)
|
||||
resMessageIsDonePromise(toolCall) // resolve with tool calls
|
||||
|
||||
},
|
||||
onError: async (error) => {
|
||||
const messageSoFar = this.streamState[threadId]?.displayContentSoFar ?? ''
|
||||
const reasoningSoFar = this.streamState[threadId]?.reasoningSoFar ?? ''
|
||||
if (this.streamState[threadId]?.isRunning !== 'LLM') {
|
||||
console.log('Unexpected onError when', this.streamState[threadId]?.isRunning)
|
||||
return
|
||||
}
|
||||
|
||||
this._setStreamState(threadId, { displayContentSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
||||
if (nAttempts < CHAT_RETRIES) {
|
||||
nAttempts += 1
|
||||
shouldRetry = true
|
||||
await timeout(RETRY_DELAY)
|
||||
this._setStreamState(threadId, undefined) // clear later so can be interrupted
|
||||
resMessageIsDonePromise()
|
||||
}
|
||||
else {
|
||||
// const toolCallSoFar = this.streamState[threadId]?.toolCallSoFar
|
||||
// add assistant's message to chat history, and clear selection
|
||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: messageSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||
this._setStreamState(threadId, { error }, 'set')
|
||||
const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo
|
||||
this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null })
|
||||
if (toolCallSoFar) this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name })
|
||||
this._setStreamState(threadId, { isRunning: undefined, error })
|
||||
resMessageIsDonePromise()
|
||||
}
|
||||
},
|
||||
onAbort: () => {
|
||||
// stop the loop to free up the promise, but don't modify state (already handled by whatever stopped it)
|
||||
aborted = true
|
||||
this._setStreamState(threadId, undefined)
|
||||
resMessageIsDonePromise()
|
||||
this._metricsService.capture('Agent Loop Done (Aborted)', { nMessagesSent, chatMode })
|
||||
},
|
||||
})
|
||||
|
||||
// should never happen, just for safety
|
||||
if (llmCancelToken === null) {
|
||||
this._setStreamState(threadId, {
|
||||
error: { message: 'There was an unexpected error when sending your chat message.', fullError: null }
|
||||
}, 'set')
|
||||
// mark as streaming
|
||||
if (!llmCancelToken) {
|
||||
this._setStreamState(threadId, { isRunning: undefined, error: { message: 'There was an unexpected error when sending your chat message.', fullError: null } })
|
||||
break
|
||||
}
|
||||
this._setStreamState(threadId, { streamingToken: llmCancelToken }, 'merge') // new stream token for the new message
|
||||
const toolCall = await messageIsDonePromise // wait for message to complete
|
||||
this._setStreamState(threadId, { streamingToken: undefined }, 'merge') // streaming message is done
|
||||
|
||||
// this is a complete hack to make it so if an error loop was aborted, we stop (because onAbort does not get called if error happens instantly)
|
||||
// maybe we should remove all the abort stuff and just make it so that we only go by state?
|
||||
if (!this.streamState[threadId]?.isRunning) { return }
|
||||
this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: '', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => this._llmMessageService.abort(llmCancelToken)) })
|
||||
const toolCall = await messageIsDonePromise // wait for message to complete
|
||||
// ending _setStreamStates are all handled above before resMessageIsDonePromise()
|
||||
|
||||
if (aborted) { return }
|
||||
if (shouldRetry) { continue }
|
||||
if (shouldRetry) {
|
||||
await timeout(RETRY_DELAY)
|
||||
continue
|
||||
}
|
||||
|
||||
// call tool if there is one
|
||||
const tool: RawToolCallObj | undefined = toolCall
|
||||
if (tool) {
|
||||
const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, tool.name, tool.id, { preapproved: false, unvalidatedToolParams: tool.rawParams })
|
||||
if (!tool) continue // skip the next part if no tool
|
||||
|
||||
// stop if interrupted. we don't have to do this for llmMessage because we have a stream token for it and onAbort gets called, but we don't have the equivalent for tools.
|
||||
// just detect tool interruption which is the same as chat interruption right now
|
||||
if (!this.streamState[threadId]?.isRunning) { return }
|
||||
if (aborted) { return }
|
||||
if (interrupted) { return }
|
||||
const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, tool.name, tool.id, { preapproved: false, unvalidatedToolParams: tool.rawParams })
|
||||
|
||||
if (awaitingUserApproval) {
|
||||
console.log('awaiting...')
|
||||
isRunningWhenEnd = 'awaiting_user'
|
||||
}
|
||||
else {
|
||||
shouldSendAnotherMessage = true
|
||||
}
|
||||
}
|
||||
this._setStreamState(threadId, undefined)
|
||||
|
||||
if (aborted) { return }
|
||||
if (interrupted) { return }
|
||||
|
||||
if (awaitingUserApproval) { isRunningWhenEnd = 'awaiting_user' }
|
||||
else { shouldSendAnotherMessage = true }
|
||||
|
||||
} // end while (attempts)
|
||||
} // end while (send message)
|
||||
|
||||
// if awaiting user approval, keep isRunning true, else end isRunning
|
||||
this._setStreamState(threadId, { isRunning: isRunningWhenEnd }, 'merge')
|
||||
this._setStreamState(threadId, { isRunning: isRunningWhenEnd })
|
||||
|
||||
// add checkpoint before the next user message
|
||||
if (!isRunningWhenEnd)
|
||||
|
|
@ -1036,7 +1071,7 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
}
|
||||
|
||||
dismissStreamError(threadId: string): void {
|
||||
this._setStreamState(threadId, { error: undefined }, 'merge')
|
||||
this._setStreamState(threadId, undefined)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1044,13 +1079,11 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return // should never happen
|
||||
|
||||
const llmCancelToken = this.streamState[threadId]?.streamingToken // currently streaming LLM on this thread
|
||||
if (llmCancelToken === undefined && this.streamState[threadId]?.isRunning) {
|
||||
// if about to call the other LLM, just wait for and stop now
|
||||
return
|
||||
// interrupt existing stream
|
||||
if (this.streamState[threadId]?.isRunning) {
|
||||
console.log('stopping....')
|
||||
await this.abortRunning(threadId)
|
||||
}
|
||||
// stop it (this simply resolves the promise to free up space)
|
||||
if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken)
|
||||
|
||||
// add dummy before this message to keep checkpoint before user message idea consistent
|
||||
if (thread.messages.length === 0) {
|
||||
|
|
@ -1163,6 +1196,20 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
}
|
||||
|
||||
|
||||
|
||||
getRelativeStr = (uri: URI) => {
|
||||
const isInside = this._workspaceContextService.isInsideWorkspace(uri)
|
||||
if (isInside) {
|
||||
const f = this._workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath))
|
||||
if (f) { return uri.fsPath.replace(f.uri.fsPath, '') }
|
||||
else { return undefined }
|
||||
}
|
||||
else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// gets the location of codespan link so the user can click on it
|
||||
generateCodespanLink: IChatThreadService['generateCodespanLink'] = async ({ codespanStr: _codespanStr, threadId }) => {
|
||||
|
||||
|
|
@ -1267,7 +1314,7 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
false, // searchOnlyEditableRange
|
||||
false, // isRegex
|
||||
true, // matchCase
|
||||
' ', // wordSeparators
|
||||
null, //' ', // wordSeparators
|
||||
true // captureMatches
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -482,7 +482,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
})
|
||||
const includeXMLToolDefinitions = !specialToolFormat
|
||||
|
||||
const runningTerminalIds = this.terminalToolService.listTerminalIds()
|
||||
const runningTerminalIds = this.terminalToolService.listPersistentTerminalIds()
|
||||
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode, includeXMLToolDefinitions })
|
||||
return systemMessage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
// run: () => { this._commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }
|
||||
// }]
|
||||
// },
|
||||
// source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}\n\nIf this persists, feel free to [report](https://github.com/voideditor/void/issues/new) it.` : undefined
|
||||
// source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}\n\nIf this persists, feel free to [report](https://github.com/voideditor-test/void/issues/new) it.` : undefined
|
||||
// })
|
||||
// }
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -17,6 +14,7 @@ import { URI } from '../../../../../../../base/common/uri.js'
|
|||
import { isAbsolute } from '../../../../../../../base/common/path.js'
|
||||
import { separateOutFirstLine } from '../../../../common/helpers/util.js'
|
||||
import { BlockCode } from '../util/inputs.js'
|
||||
import { CodespanLocationLink } from '../../../../common/chatThreadServiceTypes.js'
|
||||
|
||||
|
||||
export type ChatMessageLocation = {
|
||||
|
|
@ -36,59 +34,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 }) => {
|
||||
|
|
@ -116,7 +114,7 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
|||
|
||||
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
||||
|
||||
let link = undefined
|
||||
let link: CodespanLocationLink | undefined = undefined
|
||||
if (rawText.endsWith('`')) { // if codespan was completed
|
||||
|
||||
// get link from cache
|
||||
|
|
@ -124,12 +122,12 @@ const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string
|
|||
|
||||
if (link === undefined) {
|
||||
// if no link, generate link and add to cache
|
||||
(chatThreadService.generateCodespanLink({ codespanStr: text, threadId })
|
||||
chatThreadService.generateCodespanLink({ codespanStr: text, threadId })
|
||||
.then(link => {
|
||||
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
||||
setDidComputeCodespanLink(true) // rerender
|
||||
})
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -546,6 +544,7 @@ const RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ..
|
|||
|
||||
|
||||
export const ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options }: { string: string, inPTag?: boolean, codeURI?: URI, chatMessageLocation: ChatMessageLocation | undefined } & RenderTokenOptions) => {
|
||||
string = string.replaceAll('\n•', '\n\n•')
|
||||
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
|
|||
import { useRefState } from '../util/helpers.js';
|
||||
import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
|
||||
|
||||
|
||||
|
||||
|
||||
export const QuickEditChat = ({
|
||||
diffareaid,
|
||||
onChangeHeight,
|
||||
|
|
|
|||
|
|
@ -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 { persistentTerminalNameOfId } from '../../../terminalToolService.js';
|
||||
|
||||
|
||||
|
||||
|
|
@ -351,7 +352,7 @@ export const VoidChatArea: React.FC<VoidChatAreaProps> = ({
|
|||
<div className='flex flex-col gap-y-1'>
|
||||
<ReasoningOptionSlider featureName={featureName} />
|
||||
|
||||
<div className='flex items-center flex-wrap gap-x-2 gap-y-1 text-nowrap flex-nowrap'>
|
||||
<div className='flex items-center gap-x-2 gap-y-1 text-nowrap flex-nowrap'>
|
||||
{featureName === 'Chat' && <ChatModeDropdown className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-2 rounded py-0.5 px-1' />}
|
||||
<ModelDropdown featureName={featureName} className='text-xs text-void-fg-3 bg-void-bg-1 rounded' />
|
||||
</div>
|
||||
|
|
@ -464,19 +465,10 @@ const ScrollToBottomContainer = ({ children, className, style, scrollContainerRe
|
|||
);
|
||||
};
|
||||
|
||||
const getRelative = (uri: URI, accessor: ReturnType<typeof useAccessor>) => {
|
||||
const workspaceContextService = accessor.get('IWorkspaceContextService')
|
||||
let path: string
|
||||
const isInside = workspaceContextService.isInsideWorkspace(uri)
|
||||
if (isInside) {
|
||||
const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath))
|
||||
if (f) { path = uri.fsPath.replace(f.uri.fsPath, '') }
|
||||
else { path = uri.fsPath }
|
||||
}
|
||||
else {
|
||||
path = uri.fsPath
|
||||
}
|
||||
return path || undefined
|
||||
|
||||
export const getRelative = (uri: URI, accessor: ReturnType<typeof useAccessor>) => {
|
||||
const chatThreadService = accessor.get('IChatThreadService')
|
||||
return chatThreadService.getRelativeStr(uri) || uri.fsPath
|
||||
}
|
||||
|
||||
export const getFolderName = (pathStr: string) => {
|
||||
|
|
@ -683,6 +675,7 @@ type ToolHeaderParams = {
|
|||
children?: React.ReactNode;
|
||||
bottomChildren?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
desc2OnClick?: () => void;
|
||||
isOpen?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
|
@ -700,6 +693,7 @@ const ToolHeaderWrapper = ({
|
|||
bottomChildren,
|
||||
isError,
|
||||
onClick,
|
||||
desc2OnClick,
|
||||
isOpen,
|
||||
isRejected,
|
||||
className, // applies to the main content
|
||||
|
|
@ -769,7 +763,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 && (
|
||||
|
|
@ -925,7 +919,7 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, curr
|
|||
|
||||
// cancel any streams on this thread
|
||||
const threadId = chatThreadsService.state.currentThreadId
|
||||
chatThreadsService.stopRunning(threadId)
|
||||
await chatThreadsService.abortRunning(threadId)
|
||||
|
||||
// update state
|
||||
setIsBeingEdited(false)
|
||||
|
|
@ -942,9 +936,9 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, curr
|
|||
requestAnimationFrame(() => _scrollToBottom?.())
|
||||
}
|
||||
|
||||
const onAbort = () => {
|
||||
const onAbort = async () => {
|
||||
const threadId = chatThreadsService.state.currentThreadId
|
||||
chatThreadsService.stopRunning(threadId)
|
||||
await chatThreadsService.abortRunning(threadId)
|
||||
}
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
|
|
@ -1231,9 +1225,13 @@ const titleOfToolName = {
|
|||
'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) },
|
||||
'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) },
|
||||
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
|
||||
|
||||
'run_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
|
||||
'run_persistent_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
|
||||
|
||||
'open_persistent_terminal': { done: `Opened terminal`, proposed: 'Open terminal', running: loadingTitleWrapper('Opening terminal') },
|
||||
'kill_persistent_terminal': { done: `Killed terminal`, proposed: 'Kill terminal', running: loadingTitleWrapper('Killing terminal') },
|
||||
|
||||
'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') },
|
||||
'search_in_file': { done: 'Searched in file', proposed: 'Search in file', running: loadingTitleWrapper('Searching in file') },
|
||||
} as const satisfies Record<ToolName, { done: any, proposed: any, running: any }>
|
||||
|
|
@ -1289,7 +1287,6 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
|
|||
const toolParams = _toolParams as ToolCallParams['search_in_file'];
|
||||
return {
|
||||
desc1: `"${toolParams.query}"`,
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
};
|
||||
},
|
||||
'create_file_or_folder': () => {
|
||||
|
|
@ -1317,7 +1314,12 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
|
|||
const toolParams = _toolParams as ToolCallParams['run_command']
|
||||
return {
|
||||
desc1: `"${toolParams.command}"`,
|
||||
desc1Info: toolParams.bgTerminalId
|
||||
}
|
||||
},
|
||||
'run_persistent_command': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['run_persistent_command']
|
||||
return {
|
||||
desc1: `"${toolParams.command}"`,
|
||||
}
|
||||
},
|
||||
'open_persistent_terminal': () => {
|
||||
|
|
@ -1326,7 +1328,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']
|
||||
|
|
@ -1537,6 +1539,105 @@ const CanceledTool = ({ toolName }: { toolName: ToolName }) => {
|
|||
}
|
||||
|
||||
|
||||
|
||||
const CommandTool = ({ toolMessage, type, threadId }: { threadId: string } & ({
|
||||
toolMessage: Exclude<ToolMessage<'run_command'>, { type: 'invalid_params' }>
|
||||
type: 'run_command'
|
||||
} | {
|
||||
toolMessage: Exclude<ToolMessage<'run_persistent_command'>, { type: 'invalid_params' }>
|
||||
type: | 'run_persistent_command'
|
||||
})) => {
|
||||
const accessor = useAccessor()
|
||||
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const toolsService = accessor.get('IToolsService')
|
||||
const terminalService = accessor.get('ITerminalService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const title = getTitle(toolMessage)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
const streamState = useChatThreadsStreamState(threadId)
|
||||
|
||||
const divRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
|
||||
const effect = async () => {
|
||||
if (streamState?.isRunning !== 'tool') return
|
||||
if (type !== 'run_command' || toolMessage.type !== 'running_now') return;
|
||||
|
||||
// wait for the interruptor so we know it's running
|
||||
|
||||
await streamState?.interrupt
|
||||
const container = divRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const terminal = terminalToolsService.getTemporaryTerminal(toolMessage.params.terminalId);
|
||||
if (!terminal) return;
|
||||
|
||||
terminal.detachFromElement();
|
||||
terminal.attachToElement(container);
|
||||
|
||||
// Listen for size changes
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
const height = entries[0].borderBoxSize[0].blockSize
|
||||
const width = entries[0].borderBoxSize[0].inlineSize
|
||||
// Layout terminal to fit container dimensions
|
||||
if (typeof terminal.layout === 'function') {
|
||||
terminalService.setActiveInstance(terminal)
|
||||
terminal.attachToElement(container);
|
||||
terminal.layout({ width, height });
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
resizeObserver.observe(container);
|
||||
return () => { terminal.detachFromElement(); resizeObserver?.disconnect(); }
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
effect()
|
||||
}, [terminalToolsService, toolMessage, toolMessage.type, type]);
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
|
||||
// it's unclear that this is a button and not an icon.
|
||||
// componentParams.desc2 = <JumpToTerminalButton
|
||||
// onClick={() => { terminalToolsService.openTerminal(terminalId) }}
|
||||
// />
|
||||
|
||||
let msg: string
|
||||
if (type === 'run_command') msg = toolsService.stringOfResult['run_command'](toolMessage.params, result)
|
||||
else msg = toolsService.stringOfResult['run_persistent_command'](toolMessage.params, result)
|
||||
|
||||
componentParams.children = <ToolChildrenWrapper className='whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
<div className='!select-text cursor-auto'>
|
||||
<BlockCode initValue={`${msg.trim()}`} language='shellscript' />
|
||||
</div>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
{result}
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else if (toolMessage.type === 'running_now') {
|
||||
componentParams.children = <div ref={divRef} className='relative h-[300px] text-sm' />
|
||||
}
|
||||
else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_request') {
|
||||
}
|
||||
|
||||
return <>
|
||||
<ToolHeaderWrapper {...componentParams} isOpen={toolMessage.type === 'running_now' ? true : undefined} />
|
||||
</>
|
||||
}
|
||||
|
||||
type ResultWrapper<T extends ToolName> = (props: { toolMessage: Exclude<ToolMessage<T>, { type: 'invalid_params' }>, messageIdx: number, threadId: string }) => React.ReactNode
|
||||
const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>, } } = {
|
||||
'read_file': {
|
||||
|
|
@ -1549,13 +1650,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor);
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) {
|
||||
const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}`
|
||||
|
|
@ -1594,13 +1695,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (params.uri) {
|
||||
const rel = getRelative(params.uri, accessor)
|
||||
|
|
@ -1642,13 +1743,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (params.uri) {
|
||||
const rel = getRelative(params.uri, accessor)
|
||||
|
|
@ -1692,16 +1793,16 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const title = getTitle(toolMessage)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (params.includePattern) {
|
||||
componentParams.info = `Only search in ${params.includePattern}`
|
||||
|
|
@ -1741,16 +1842,16 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const title = getTitle(toolMessage)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (params.searchInFolder || params.isRegex) {
|
||||
let info: string[] = []
|
||||
|
|
@ -1758,9 +1859,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const rel = getRelative(params.searchInFolder, accessor)
|
||||
if (rel) info.push(`Only search in ${rel}`)
|
||||
}
|
||||
if (params.isRegex) {
|
||||
info.push(`Treat as regex`)
|
||||
}
|
||||
if (params.isRegex) { info.push(`Treat search as regex`) }
|
||||
componentParams.info = info.join('; ')
|
||||
}
|
||||
|
||||
|
|
@ -1798,15 +1897,22 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const accessor = useAccessor();
|
||||
const toolsService = accessor.get('IToolsService');
|
||||
const title = getTitle(toolMessage);
|
||||
const isError = toolMessage.type === 'tool_error';
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor);
|
||||
const icon = null;
|
||||
|
||||
if (toolMessage.type === 'tool_request' || toolMessage.type === 'rejected' || toolMessage.type === 'running_now') return null;
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error';
|
||||
const { rawParams, params } = toolMessage;
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon };
|
||||
if (params.isRegex) componentParams.info = 'Treat as regex'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected };
|
||||
|
||||
const infoarr: string[] = []
|
||||
const uriStr = getRelative(params.uri, accessor)
|
||||
if (uriStr) infoarr.push(uriStr)
|
||||
if (params.isRegex) infoarr.push('Treat search as regex')
|
||||
componentParams.info = infoarr.join('; ')
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage; // result is array of snippets
|
||||
|
|
@ -1844,13 +1950,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
componentParams.info = getRelative(uri, accessor) // full path
|
||||
|
||||
|
|
@ -2049,68 +2155,16 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
// ---
|
||||
|
||||
'run_command': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const title = getTitle(toolMessage)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
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
|
||||
|
||||
componentParams.children = <ToolChildrenWrapper className='whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
<div className='!select-text cursor-auto'>
|
||||
<div>
|
||||
<span className="text-void-fg-1 font-sans">{`Ran command: `}</span>
|
||||
<span className="font-mono">{command}</span>
|
||||
</div>
|
||||
{(terminalResult + additionalDetailsStr).length && <div>
|
||||
<span className='text-void-fg-1'>{`Result: `}</span>
|
||||
<span className="font-mono">{terminalResult}</span>
|
||||
<span className="font-mono">{additionalDetailsStr}</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>
|
||||
}
|
||||
}
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
resultWrapper: (params) => {
|
||||
return <CommandTool {...params} type='run_command' />
|
||||
}
|
||||
},
|
||||
|
||||
'run_persistent_command': {
|
||||
resultWrapper: (params) => {
|
||||
return <CommandTool {...params} type='run_persistent_command' />
|
||||
}
|
||||
},
|
||||
'open_persistent_terminal': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -2120,22 +2174,20 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const title = getTitle(toolMessage)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
componentParams.info = params.cwd ? `Running in ${getRelative(URI.file(params.cwd), accessor)}` : ''
|
||||
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 = persistentTerminalNameOfId(persistentTerminalId)
|
||||
componentParams.onClick = () => terminalToolsService.focusPersistentTerminal(persistentTerminalId)
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
|
|
@ -2153,21 +2205,24 @@ 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)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
const { persistentTerminalId } = params
|
||||
componentParams.desc1 = persistentTerminalNameOfId(persistentTerminalId)
|
||||
componentParams.onClick = () => terminalToolsService.focusPersistentTerminal(persistentTerminalId)
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
|
|
@ -2686,9 +2741,7 @@ export const SidebarChat = () => {
|
|||
const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)
|
||||
const isRunning = currThreadStreamState?.isRunning
|
||||
const latestError = currThreadStreamState?.error
|
||||
const displayContentSoFar = currThreadStreamState?.displayContentSoFar
|
||||
const toolCallSoFar = currThreadStreamState?.toolCallSoFar
|
||||
const reasoningSoFar = currThreadStreamState?.reasoningSoFar
|
||||
const { displayContentSoFar, toolCallSoFar, reasoningSoFar } = currThreadStreamState?.llmInfo ?? {}
|
||||
|
||||
// this is just if it's currently being generated, NOT if it's currently running
|
||||
const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone && toolCallSoFar.name === 'edit_file' // show loading for slow tools (right now just edit)
|
||||
|
|
@ -2726,9 +2779,9 @@ export const SidebarChat = () => {
|
|||
|
||||
}, [chatThreadsService, isDisabled, isRunning, textAreaRef, textAreaFnsRef, setSelections, settingsState])
|
||||
|
||||
const onAbort = () => {
|
||||
const onAbort = async () => {
|
||||
const threadId = currentThread.id
|
||||
chatThreadsService.stopRunning(threadId)
|
||||
await chatThreadsService.abortRunning(threadId)
|
||||
}
|
||||
|
||||
const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_L_ACTION_ID)?.getLabel()
|
||||
|
|
|
|||
|
|
@ -467,7 +467,7 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
|
|||
rows={1}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<div>{`idx ${optionIdx}`}</div>
|
||||
{/* <div>{`idx ${optionIdx}`}</div> */}
|
||||
{isMenuOpen && (
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import { INativeHostService } from '../../../../../../../platform/native/common/
|
|||
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
|
||||
import { IToolsService } from '../../../toolsService.js'
|
||||
import { IConvertToLLMMessageService } from '../../../convertToLLMMessageService.js'
|
||||
import { ITerminalService } from '../../../../../terminal/browser/terminal.js'
|
||||
|
||||
|
||||
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
|
||||
|
|
@ -145,8 +146,8 @@ export const _registerServices = (accessor: ServicesAccessor) => {
|
|||
|
||||
colorThemeState = themeService.getColorTheme().type
|
||||
disposables.push(
|
||||
themeService.onDidColorThemeChange(({ theme }) => {
|
||||
colorThemeState = theme.type
|
||||
themeService.onDidColorThemeChange(({ type }) => {
|
||||
colorThemeState = type
|
||||
colorThemeStateListeners.forEach(l => l(colorThemeState))
|
||||
})
|
||||
)
|
||||
|
|
@ -219,6 +220,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
|
|||
INativeHostService: accessor.get(INativeHostService),
|
||||
IToolsService: accessor.get(IToolsService),
|
||||
IConvertToLLMMessageService: accessor.get(IConvertToLLMMessageService),
|
||||
ITerminalService: accessor.get(ITerminalService),
|
||||
|
||||
} as const
|
||||
return reactAccessor
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ const VoidOnboardingContent = () => {
|
|||
// can be md
|
||||
const detailedDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
|
||||
smart: "Most intelligent and best for agent mode.",
|
||||
private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor.com) for help setting up at your company.",
|
||||
private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor-test.com) for help setting up at your company.",
|
||||
cheap: "Use great deals like Gemini 2.5 Pro, or self-host a model with Ollama or vLLM for free.",
|
||||
all: "",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
|
||||
import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
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 { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { ITerminalService, ITerminalInstance, ICreateTerminalOptions } from '../../../../workbench/contrib/terminal/browser/terminal.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';
|
||||
|
||||
|
||||
|
|
@ -17,13 +19,16 @@ import { TerminalResolveReason } from '../common/toolsServiceTypes.js';
|
|||
export interface ITerminalToolService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
listTerminalIds(): string[];
|
||||
runCommand(command: string, bgTerminalId: string | null): Promise<{ terminalId: string, resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>;
|
||||
focusTerminal(terminalId: string): Promise<void>
|
||||
terminalExists(terminalId: string): boolean
|
||||
listPersistentTerminalIds(): string[];
|
||||
runCommand(command: string, opts: { type: 'persistent', persistentTerminalId: string } | { type: 'ephemeral', cwd: string | null, terminalId: string }): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>;
|
||||
focusPersistentTerminal(terminalId: string): Promise<void>
|
||||
persistentTerminalExists(terminalId: string): boolean
|
||||
|
||||
createTerminal(): Promise<string>
|
||||
killTerminal(terminalId: string): Promise<void>
|
||||
createPersistentTerminal(opts: { cwd: string | null }): Promise<string>
|
||||
killPersistentTerminal(terminalId: string): Promise<void>
|
||||
|
||||
getPersistentTerminal(terminalId: string): ITerminalInstance | undefined
|
||||
getTemporaryTerminal(terminalId: string): ITerminalInstance | undefined
|
||||
}
|
||||
|
||||
export const ITerminalToolService = createDecorator<ITerminalToolService>('TerminalToolService');
|
||||
|
|
@ -39,11 +44,11 @@ function isCommandComplete(output: string) {
|
|||
}
|
||||
|
||||
|
||||
const nameOfId = (id: string) => {
|
||||
export const persistentTerminalNameOfId = (id: string) => {
|
||||
if (id === '1') return 'Void Agent'
|
||||
return `Void Agent (${id})`
|
||||
}
|
||||
const idOfName = (name: string) => {
|
||||
export const idOfPersistentTerminalName = (name: string) => {
|
||||
if (name === 'Void Agent') return '1'
|
||||
|
||||
const match = name.match(/Void Agent \((\d+)\)/)
|
||||
|
|
@ -55,10 +60,12 @@ const idOfName = (name: string) => {
|
|||
export class TerminalToolService extends Disposable implements ITerminalToolService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
private terminalInstanceOfId: Record<string, ITerminalInstance> = {}
|
||||
private persistentTerminalInstanceOfId: Record<string, ITerminalInstance> = {}
|
||||
private temporaryTerminalInstanceOfId: Record<string, ITerminalInstance> = {}
|
||||
|
||||
constructor(
|
||||
@ITerminalService private readonly terminalService: ITerminalService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
|
@ -66,8 +73,8 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
const initializeTerminal = (terminal: ITerminalInstance) => {
|
||||
// when exit, remove
|
||||
const d = terminal.onExit(() => {
|
||||
const terminalId = idOfName(terminal.title)
|
||||
if (terminalId !== null && (terminalId in this.terminalInstanceOfId)) delete this.terminalInstanceOfId[terminalId]
|
||||
const terminalId = idOfPersistentTerminalName(terminal.title)
|
||||
if (terminalId !== null && (terminalId in this.persistentTerminalInstanceOfId)) delete this.persistentTerminalInstanceOfId[terminalId]
|
||||
d.dispose()
|
||||
})
|
||||
}
|
||||
|
|
@ -75,8 +82,8 @@ 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)
|
||||
if (proposedTerminalId) this.terminalInstanceOfId[proposedTerminalId] = terminal
|
||||
const proposedTerminalId = idOfPersistentTerminalName(terminal.title)
|
||||
if (proposedTerminalId) this.persistentTerminalInstanceOfId[proposedTerminalId] = terminal
|
||||
|
||||
initializeTerminal(terminal)
|
||||
}
|
||||
|
|
@ -88,32 +95,36 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
}
|
||||
|
||||
|
||||
listTerminalIds() {
|
||||
return Object.keys(this.terminalInstanceOfId)
|
||||
listPersistentTerminalIds() {
|
||||
return Object.keys(this.persistentTerminalInstanceOfId)
|
||||
}
|
||||
|
||||
getValidNewTerminalId(): string {
|
||||
// {1 2 3} # size 3, new=4
|
||||
// {1 3 4} # size 3, new=2
|
||||
// 1 <= newTerminalId <= n + 1
|
||||
const n = Object.keys(this.terminalInstanceOfId).length;
|
||||
const n = Object.keys(this.persistentTerminalInstanceOfId).length;
|
||||
if (n === 0) return '1'
|
||||
|
||||
for (let i = 1; i <= n + 1; i++) {
|
||||
const potentialId = i + '';
|
||||
if (!(potentialId in this.terminalInstanceOfId)) return potentialId;
|
||||
if (!(potentialId in this.persistentTerminalInstanceOfId)) return potentialId;
|
||||
}
|
||||
throw new Error('This should never be reached by pigeonhole principle');
|
||||
}
|
||||
|
||||
async createTerminal() {
|
||||
// create new terminal and return its ID
|
||||
const terminalId = this.getValidNewTerminalId();
|
||||
const terminal = await this.terminalService.createTerminal({
|
||||
location: TerminalLocation.Panel,
|
||||
config: { name: nameOfId(terminalId), title: nameOfId(terminalId) },
|
||||
})
|
||||
|
||||
private async _createTerminal(props: { cwd: string | null, config: ICreateTerminalOptions['config'] }) {
|
||||
const { cwd: override_cwd, config } = props
|
||||
|
||||
const cwd: URI | string | undefined = (override_cwd ?? undefined) ?? this.workspaceContextService.getWorkspace().folders[0]?.uri
|
||||
|
||||
// create new terminal and return its ID
|
||||
const terminal = await this.terminalService.createTerminal({
|
||||
cwd: cwd,
|
||||
location: TerminalLocation.Panel,
|
||||
config: config,
|
||||
})
|
||||
|
||||
// when a new terminal is created, there is an initial command that gets run which is empty, wait for it to end before returning
|
||||
const disposables: IDisposable[] = []
|
||||
|
|
@ -130,26 +141,49 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
await Promise.any([waitForMount, waitForTimeout,])
|
||||
disposables.forEach(d => d.dispose())
|
||||
|
||||
this.terminalInstanceOfId[terminalId] = terminal
|
||||
return terminal
|
||||
|
||||
}
|
||||
|
||||
createPersistentTerminal: ITerminalToolService['createPersistentTerminal'] = async ({ cwd }) => {
|
||||
const terminalId = this.getValidNewTerminalId();
|
||||
const config = { name: persistentTerminalNameOfId(terminalId), title: persistentTerminalNameOfId(terminalId) }
|
||||
const terminal = await this._createTerminal({ cwd, config, })
|
||||
this.persistentTerminalInstanceOfId[terminalId] = terminal
|
||||
return terminalId
|
||||
}
|
||||
|
||||
async killTerminal(terminalId: string) {
|
||||
const terminal = this.terminalInstanceOfId[terminalId]
|
||||
async killPersistentTerminal(terminalId: string) {
|
||||
const terminal = this.persistentTerminalInstanceOfId[terminalId]
|
||||
if (!terminal) throw new Error(`Kill Terminal: Terminal with ID ${terminalId} did not exist.`);
|
||||
terminal.dispose(TerminalExitReason.Extension)
|
||||
delete this.terminalInstanceOfId[terminalId]
|
||||
terminal.dispose()
|
||||
delete this.persistentTerminalInstanceOfId[terminalId]
|
||||
return
|
||||
}
|
||||
|
||||
terminalExists(terminalId: string): boolean {
|
||||
return terminalId in this.terminalInstanceOfId
|
||||
persistentTerminalExists(terminalId: string): boolean {
|
||||
return terminalId in this.persistentTerminalInstanceOfId
|
||||
}
|
||||
|
||||
|
||||
focusTerminal: ITerminalToolService['focusTerminal'] = async (terminalId) => {
|
||||
getTemporaryTerminal(terminalId: string): ITerminalInstance | undefined {
|
||||
if (!terminalId) return
|
||||
const terminal = this.terminalInstanceOfId[terminalId]
|
||||
const terminal = this.temporaryTerminalInstanceOfId[terminalId]
|
||||
if (!terminal) return // should never happen
|
||||
return terminal
|
||||
}
|
||||
|
||||
getPersistentTerminal(terminalId: string): ITerminalInstance | undefined {
|
||||
if (!terminalId) return
|
||||
const terminal = this.persistentTerminalInstanceOfId[terminalId]
|
||||
if (!terminal) return // should never happen
|
||||
return terminal
|
||||
}
|
||||
|
||||
|
||||
focusPersistentTerminal: ITerminalToolService['focusPersistentTerminal'] = async (terminalId) => {
|
||||
if (!terminalId) return
|
||||
const terminal = this.persistentTerminalInstanceOfId[terminalId]
|
||||
if (!terminal) return // should never happen
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
|
|
@ -158,30 +192,38 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
|
||||
|
||||
|
||||
runCommand: ITerminalToolService['runCommand'] = async (command, bgTerminalId) => {
|
||||
runCommand: ITerminalToolService['runCommand'] = async (command, params) => {
|
||||
const { type } = params
|
||||
await this.terminalService.whenConnected;
|
||||
|
||||
let terminal: ITerminalInstance
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
const isBG = bgTerminalId !== null
|
||||
let terminalId: string
|
||||
if (isBG) { // BG process
|
||||
terminal = this.terminalInstanceOfId[bgTerminalId];
|
||||
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${bgTerminalId} did not exist.`);
|
||||
terminalId = bgTerminalId
|
||||
const isPersistent = type === 'persistent'
|
||||
|
||||
if (isPersistent) { // BG process
|
||||
const { persistentTerminalId } = params
|
||||
terminal = this.persistentTerminalInstanceOfId[persistentTerminalId];
|
||||
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${persistentTerminalId} did not exist.`);
|
||||
}
|
||||
else {
|
||||
terminalId = await this.createTerminal()
|
||||
terminal = this.terminalInstanceOfId[terminalId]
|
||||
if (!terminal) throw new Error(`Unexpected error: Terminal could not be created.`)
|
||||
const { cwd } = params
|
||||
terminal = await this._createTerminal({ cwd: cwd, config: { name: 'Void Temporary Terminal', title: 'Void Temporary Terminal' } })
|
||||
this.temporaryTerminalInstanceOfId[params.terminalId] = terminal
|
||||
}
|
||||
|
||||
const interrupt = () => {
|
||||
terminal.dispose()
|
||||
if (!isPersistent)
|
||||
delete this.temporaryTerminalInstanceOfId[params.terminalId]
|
||||
}
|
||||
|
||||
const waitForResult = async () => {
|
||||
// focus the terminal about to run
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
if (isPersistent) {
|
||||
// focus the terminal about to run
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
}
|
||||
|
||||
let result: string = ''
|
||||
let resolveReason: TerminalResolveReason | undefined = undefined
|
||||
|
|
@ -207,30 +249,38 @@ 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 = isPersistent ?
|
||||
// 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) {
|
||||
await this.killTerminal(terminalId)
|
||||
if (!isPersistent) {
|
||||
interrupt()
|
||||
}
|
||||
|
||||
if (!resolveReason) throw new Error('Unexpected internal error: Promise.any should have resolved with a reason.')
|
||||
|
|
@ -251,7 +301,10 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
}
|
||||
const resPromise = waitForResult()
|
||||
|
||||
return { terminalId, resPromise }
|
||||
return {
|
||||
interrupt,
|
||||
resPromise,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ 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'
|
||||
import { generateUuid } from '../../../../base/common/uuid.js'
|
||||
|
||||
|
||||
// tool use for AI
|
||||
|
|
@ -83,16 +84,8 @@ const validateNumber = (numStr: unknown, opts: { default: number | null }) => {
|
|||
return opts.default
|
||||
}
|
||||
|
||||
const validateRecursiveParamStr = (paramsUnknown: unknown) => {
|
||||
if (!paramsUnknown) return false
|
||||
if (typeof paramsUnknown !== 'string') throw new Error('Invalid LLM output format: Error calling tool: provided params must be a string.')
|
||||
const params = paramsUnknown
|
||||
const isRecursive = params.includes('r')
|
||||
return isRecursive
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -233,9 +226,9 @@ export class ToolsService implements IToolsService {
|
|||
},
|
||||
|
||||
delete_file_or_folder: (params: RawToolParamsObj) => {
|
||||
const { uri: uriUnknown, params: paramsStr } = params
|
||||
const { uri: uriUnknown, is_recursive: isRecursiveUnknown } = params
|
||||
const uri = validateURI(uriUnknown)
|
||||
const isRecursive = validateRecursiveParamStr(paramsStr)
|
||||
const isRecursive = validateBoolean(isRecursiveUnknown, { default: false })
|
||||
const uriStr = validateStr('uri', uriUnknown)
|
||||
const isFolder = checkIfIsFolder(uriStr)
|
||||
return { uri, isRecursive, isFolder }
|
||||
|
|
@ -251,19 +244,28 @@ export class ToolsService implements IToolsService {
|
|||
// ---
|
||||
|
||||
run_command: (params: RawToolParamsObj) => {
|
||||
const { command: commandUnknown, terminal_id: terminalIdUnknown } = params;
|
||||
const command = validateStr('command', commandUnknown);
|
||||
const proposedTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null;
|
||||
return { command, bgTerminalId: proposedTerminalId };
|
||||
const { command: commandUnknown, cwd: cwdUnknown } = params
|
||||
const command = validateStr('command', commandUnknown)
|
||||
const cwd = validateOptionalStr('cwd', cwdUnknown)
|
||||
const terminalId = generateUuid()
|
||||
return { command, cwd, terminalId }
|
||||
},
|
||||
open_persistent_terminal: (_params: RawToolParamsObj) => {
|
||||
run_persistent_command: (params: RawToolParamsObj) => {
|
||||
const { command: commandUnknown, persistent_terminal_id: persistentTerminalIdUnknown } = params;
|
||||
const command = validateStr('command', commandUnknown);
|
||||
const persistentTerminalId = validateProposedTerminalId(persistentTerminalIdUnknown)
|
||||
return { command, persistentTerminalId };
|
||||
},
|
||||
open_persistent_terminal: (params: RawToolParamsObj) => {
|
||||
const { cwd: cwdUnknown } = params;
|
||||
const cwd = validateOptionalStr('cwd', cwdUnknown)
|
||||
// No parameters needed; will open a new background terminal
|
||||
return {};
|
||||
return { cwd };
|
||||
},
|
||||
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 };
|
||||
},
|
||||
|
||||
}
|
||||
|
|
@ -414,21 +416,23 @@ export class ToolsService implements IToolsService {
|
|||
return { result: lintErrorsPromise, interruptTool }
|
||||
},
|
||||
// ---
|
||||
run_command: async ({ command, bgTerminalId }) => {
|
||||
const { terminalId, resPromise } = await this.terminalToolService.runCommand(command, bgTerminalId)
|
||||
const interruptTool = () => {
|
||||
this.terminalToolService.killTerminal(terminalId)
|
||||
}
|
||||
return { result: resPromise, interruptTool }
|
||||
run_command: async ({ command, cwd, terminalId }) => {
|
||||
const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'ephemeral', cwd, terminalId })
|
||||
console.log('qqq', interrupt)
|
||||
return { result: resPromise, interruptTool: interrupt }
|
||||
},
|
||||
open_persistent_terminal: async () => {
|
||||
// Open a new background terminal without waiting for completion
|
||||
const terminalId = await this.terminalToolService.createTerminal()
|
||||
return { result: { terminalId } }
|
||||
run_persistent_command: async ({ command, persistentTerminalId }) => {
|
||||
const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'persistent', persistentTerminalId })
|
||||
console.log('qqq', interrupt)
|
||||
return { result: resPromise, interruptTool: interrupt }
|
||||
},
|
||||
kill_persistent_terminal: async ({ terminalId }) => {
|
||||
open_persistent_terminal: async ({ cwd }) => {
|
||||
const persistentTerminalId = await this.terminalToolService.createPersistentTerminal({ cwd })
|
||||
return { result: { persistentTerminalId } }
|
||||
},
|
||||
kill_persistent_terminal: async ({ persistentTerminalId }) => {
|
||||
// Close the background terminal by sending exit
|
||||
await this.terminalToolService.killTerminal(terminalId)
|
||||
await this.terminalToolService.killPersistentTerminal(persistentTerminalId)
|
||||
return { result: {} }
|
||||
},
|
||||
|
||||
|
|
@ -493,39 +497,38 @@ export class ToolsService implements IToolsService {
|
|||
return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}`
|
||||
},
|
||||
run_command: (params, result) => {
|
||||
const {
|
||||
resolveReason,
|
||||
result: result_,
|
||||
} = result
|
||||
const { bgTerminalId } = params
|
||||
|
||||
const { resolveReason, result: result_, } = result
|
||||
// success
|
||||
if (resolveReason.type === 'done') {
|
||||
const desc = bgTerminalId ? ` in terminal ${bgTerminalId}` : ''
|
||||
return `Terminal command executed and finished${desc}. Result (exit code ${resolveReason.exitCode}):\n${result_}`
|
||||
}
|
||||
|
||||
// bg command
|
||||
if (bgTerminalId !== 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 `${result_}\n(exit code ${resolveReason.exitCode})`
|
||||
}
|
||||
// normal command
|
||||
else {
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `Terminal command ran, but was interrupted after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not necessarily finish successfully. Full output:\n${result_}`
|
||||
}
|
||||
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.`
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`)
|
||||
},
|
||||
|
||||
run_persistent_command: (params, result) => {
|
||||
const { resolveReason, result: result_, } = result
|
||||
const { persistentTerminalId } = params
|
||||
// success
|
||||
if (resolveReason.type === 'done') {
|
||||
return `${result_}\n(exit code ${resolveReason.exitCode})`
|
||||
}
|
||||
// bg command
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `${result_}\nTerminal command is running in terminal ${persistentTerminalId}. The given outputs are the results after ${MAX_TERMINAL_BG_COMMAND_TIME} seconds.`
|
||||
}
|
||||
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}".`;
|
||||
},
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { IAction } from '../../../../base/common/actions.js';
|
|||
|
||||
|
||||
const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService) => {
|
||||
const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!'
|
||||
const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor-test.com/download-beta)!'
|
||||
|
||||
let actions: INotificationActions | undefined
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
|
|||
class: undefined,
|
||||
run: () => {
|
||||
const { window } = dom.getActiveWindow()
|
||||
window.open('https://voideditor.com/download-beta')
|
||||
window.open('https://voideditor-test.com/download-beta')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
|
|||
class: undefined,
|
||||
run: () => {
|
||||
const { window } = dom.getActiveWindow()
|
||||
window.open('https://voideditor.com/')
|
||||
window.open('https://voideditor-test.com/')
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
|
|||
// })
|
||||
}
|
||||
const notifyErrChecking = (notifService: INotificationService) => {
|
||||
const message = `Void Error: There was an error checking for updates. If this persists, please get in touch or reinstall Void [here](https://voideditor.com/download-beta)!`
|
||||
const message = `Void Error: There was an error checking for updates. If this persists, please get in touch or reinstall Void [here](https://voideditor-test.com/download-beta)!`
|
||||
notifService.notify({
|
||||
severity: Severity.Info,
|
||||
message: message,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export const defaultModelsOfProvider = {
|
|||
],
|
||||
gemini: [ // https://ai.google.dev/gemini-api/docs/models/gemini
|
||||
'gemini-2.5-pro-exp-03-25',
|
||||
'gemini-2.0-flash',
|
||||
'gemini-2.5-flash-preview-04-17',
|
||||
'gemini-2.0-flash-lite',
|
||||
],
|
||||
deepseek: [ // https://api-docs.deepseek.com/quick_start/pricing
|
||||
|
|
@ -633,6 +633,15 @@ const xAISettings: VoidStaticProviderInfo = {
|
|||
|
||||
// ---------------- GEMINI ----------------
|
||||
const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||
'gemini-2.5-flash-preview-04-17': {
|
||||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 0.15, output: .60 }, // TODO $3.50 output with thinking not included
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'gemini-2.5-pro-exp-03-25': {
|
||||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { EndOfLinePreference } from '../../../../../editor/common/model.js';
|
|||
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
|
||||
import { os } from '../helpers/systemInfo.js';
|
||||
import { RawToolParamsObj } from '../sendLLMMessageTypes.js';
|
||||
import { approvalTypeOfToolName, ToolResultType } from '../toolsServiceTypes.js';
|
||||
import { approvalTypeOfToolName, ToolCallParams, ToolResultType } from '../toolsServiceTypes.js';
|
||||
import { IVoidModelService } from '../voidModelService.js';
|
||||
import { ChatMode } from '../voidSettingsTypes.js';
|
||||
|
||||
|
|
@ -27,12 +27,14 @@ 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
|
||||
export const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
||||
|
||||
|
||||
|
||||
// ======================================================== tools ========================================================
|
||||
const changesExampleContent = `\
|
||||
// ... existing code ...
|
||||
|
|
@ -43,12 +45,12 @@ const changesExampleContent = `\
|
|||
// {{change 3}}
|
||||
// ... existing code ...`
|
||||
|
||||
const editToolDescriptionExample = `\
|
||||
const editToolDiffExample = `\
|
||||
${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]}`
|
||||
|
|
@ -74,173 +76,188 @@ const paginationParam = {
|
|||
} as const
|
||||
|
||||
|
||||
const terminalDescHelper = `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.`
|
||||
|
||||
const cwdHelper = 'Optional. The directory in which to run the command. Defaults to the first workspace folder.'
|
||||
|
||||
// export type SnakeCase<S extends string> =
|
||||
// // exact acronym URI
|
||||
// S extends 'URI' ? 'uri'
|
||||
// // suffix URI: e.g. 'rootURI' -> snakeCase('root') + '_uri'
|
||||
// : S extends `${infer Prefix}URI` ? `${SnakeCase<Prefix>}_uri`
|
||||
// // default: for each char, prefix '_' on uppercase letters
|
||||
// : S extends `${infer C}${infer Rest}`
|
||||
// ? `${C extends Lowercase<C> ? C : `_${Lowercase<C>}`}${SnakeCase<Rest>}`
|
||||
// : S;
|
||||
export type SnakeCase<S extends string> =
|
||||
// exact acronym URI
|
||||
S extends 'URI' ? 'uri'
|
||||
// suffix URI: e.g. 'rootURI' -> snakeCase('root') + '_uri'
|
||||
: S extends `${infer Prefix}URI` ? `${SnakeCase<Prefix>}_uri`
|
||||
// default: for each char, prefix '_' on uppercase letters
|
||||
: S extends `${infer C}${infer Rest}`
|
||||
? `${C extends Lowercase<C> ? C : `_${Lowercase<C>}`}${SnakeCase<Rest>}`
|
||||
: S;
|
||||
|
||||
// export type SnakeCaseKeys<T extends Record<string, any>> = {
|
||||
// [K in keyof T as SnakeCase<Extract<K, string>>]: T[K]
|
||||
// };
|
||||
export type SnakeCaseKeys<T extends Record<string, any>> = {
|
||||
[K in keyof T as SnakeCase<Extract<K, string>>]: T[K]
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const voidTools = {
|
||||
// export const voidTools
|
||||
// : {
|
||||
// [T in keyof ToolCallParams]: {
|
||||
// name: string;
|
||||
// description: string;
|
||||
// params: {
|
||||
// [paramName in keyof SnakeCaseKeys<ToolCallParams[T]>]: { description: string }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// = {
|
||||
// --- context-gathering (read/search/list) ---
|
||||
|
||||
read_file: {
|
||||
name: 'read_file',
|
||||
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.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
ls_dir: {
|
||||
name: 'ls_dir',
|
||||
description: `Lists all files and folders in the given URI.`,
|
||||
params: {
|
||||
uri: { description: `Optional. The FULL path to the ${'folder'}. Leave this as empty or "" to search all folders.` },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
get_dir_tree: {
|
||||
name: 'get_dir_tree',
|
||||
description: `This is a very effective way to learn about the user's codebase. Returns a tree diagram of all the files and folders in the given folder. `,
|
||||
params: {
|
||||
...uriParam('folder')
|
||||
}
|
||||
},
|
||||
|
||||
// pathname_search: {
|
||||
// name: 'pathname_search',
|
||||
// description: `Returns all pathnames that match a given \`find\`-style query over the entire workspace. ONLY searches file names. ONLY searches the current workspace. You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`,
|
||||
|
||||
search_pathnames_only: {
|
||||
name: 'search_pathnames_only',
|
||||
description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path.`,
|
||||
params: {
|
||||
query: { description: `Your query for the search.` },
|
||||
include_pattern: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
search_for_files: {
|
||||
name: 'search_for_files',
|
||||
description: `Returns a list of file names whose content matches the given query. The query can be any substring or regex.`,
|
||||
params: {
|
||||
query: { description: `Your query for the search.` },
|
||||
search_in_folder: { description: 'Optional. Leave as blank by default. ONLY fill this in if your previous search with the same query was truncated. Searches descendants of this folder only.' },
|
||||
is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
// add new search_in_file tool
|
||||
search_in_file: {
|
||||
name: 'search_in_file',
|
||||
description: `Returns an array of all the start line numbers where the content appears in the file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
query: { description: 'The string or regex to search for in the file.' },
|
||||
is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' }
|
||||
}
|
||||
},
|
||||
|
||||
read_lint_errors: {
|
||||
name: 'read_lint_errors',
|
||||
description: `Returns all lint errors on a given file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
},
|
||||
},
|
||||
|
||||
// --- editing (create/delete) ---
|
||||
|
||||
create_file_or_folder: {
|
||||
name: 'create_file_or_folder',
|
||||
description: `Create a file or folder at the given path. To create a folder, the path MUST end with a trailing slash.`,
|
||||
params: {
|
||||
...uriParam('file or folder'),
|
||||
},
|
||||
},
|
||||
|
||||
delete_file_or_folder: {
|
||||
name: 'delete_file_or_folder',
|
||||
description: `Delete a file or folder at the given path.`,
|
||||
params: {
|
||||
...uriParam('file or folder'),
|
||||
params: { description: 'Optional. Return -r here to delete recursively.' }
|
||||
},
|
||||
},
|
||||
|
||||
edit_file: { // APPLY TOOL
|
||||
name: 'edit_file',
|
||||
description: `Edits the contents of a file given the file's URI and a description.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
change_diff: {
|
||||
description: `\
|
||||
A code diff describing the change to make to the file. \
|
||||
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${editToolDescriptionExample}`
|
||||
Here's an example of a good output:\n${type === 'edit tool' ? editToolDiffExample : chatSuggestionDiffExample}`
|
||||
|
||||
|
||||
// export const voidTools = {
|
||||
export const voidTools
|
||||
: {
|
||||
[T in keyof ToolCallParams]: {
|
||||
name: string;
|
||||
description: string;
|
||||
// more params can be generated than exist here, but these params must be a subset of them
|
||||
params: Partial<{ [paramName in keyof SnakeCaseKeys<ToolCallParams[T]>]: { description: string } }>
|
||||
}
|
||||
}
|
||||
= {
|
||||
// --- context-gathering (read/search/list) ---
|
||||
|
||||
read_file: {
|
||||
name: 'read_file',
|
||||
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.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
ls_dir: {
|
||||
name: 'ls_dir',
|
||||
description: `Lists all files and folders in the given URI.`,
|
||||
params: {
|
||||
uri: { description: `Optional. The FULL path to the ${'folder'}. Leave this as empty or "" to search all folders.` },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
get_dir_tree: {
|
||||
name: 'get_dir_tree',
|
||||
description: `This is a very effective way to learn about the user's codebase. Returns a tree diagram of all the files and folders in the given folder. `,
|
||||
params: {
|
||||
...uriParam('folder')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
run_command: {
|
||||
name: 'run_command',
|
||||
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.' },
|
||||
// pathname_search: {
|
||||
// name: 'pathname_search',
|
||||
// description: `Returns all pathnames that match a given \`find\`-style query over the entire workspace. ONLY searches file names. ONLY searches the current workspace. You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`,
|
||||
|
||||
search_pathnames_only: {
|
||||
name: 'search_pathnames_only',
|
||||
description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path.`,
|
||||
params: {
|
||||
query: { description: `Your query for the search.` },
|
||||
include_pattern: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
open_persistent_terminal: {
|
||||
name: 'open_persistent_terminal',
|
||||
description: `Use this tool when you want to run a terminal command indefinitely, like a dev server (eg \`npm run dev\`), a background listener, etc. Opens a new terminal in the user's environment which will not awaited for or killed.`,
|
||||
params: {}
|
||||
},
|
||||
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.` } }
|
||||
}
|
||||
|
||||
|
||||
// go_to_definition
|
||||
// go_to_usages
|
||||
|
||||
} satisfies { [T in keyof ToolResultType]: InternalToolInfo }
|
||||
search_for_files: {
|
||||
name: 'search_for_files',
|
||||
description: `Returns a list of file names whose content matches the given query. The query can be any substring or regex.`,
|
||||
params: {
|
||||
query: { description: `Your query for the search.` },
|
||||
search_in_folder: { description: 'Optional. Leave as blank by default. ONLY fill this in if your previous search with the same query was truncated. Searches descendants of this folder only.' },
|
||||
is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
||||
// add new search_in_file tool
|
||||
search_in_file: {
|
||||
name: 'search_in_file',
|
||||
description: `Returns an array of all the start line numbers where the content appears in the file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
query: { description: 'The string or regex to search for in the file.' },
|
||||
is_regex: { description: 'Optional. Default is false. Whether the query is a regex.' }
|
||||
}
|
||||
},
|
||||
|
||||
read_lint_errors: {
|
||||
name: 'read_lint_errors',
|
||||
description: `Returns all lint errors on a given file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
},
|
||||
},
|
||||
|
||||
// --- editing (create/delete) ---
|
||||
|
||||
create_file_or_folder: {
|
||||
name: 'create_file_or_folder',
|
||||
description: `Create a file or folder at the given path. To create a folder, the path MUST end with a trailing slash.`,
|
||||
params: {
|
||||
...uriParam('file or folder'),
|
||||
},
|
||||
},
|
||||
|
||||
delete_file_or_folder: {
|
||||
name: 'delete_file_or_folder',
|
||||
description: `Delete a file or folder at the given path.`,
|
||||
params: {
|
||||
...uriParam('file or folder'),
|
||||
is_recursive: { description: 'Optional. Return true to delete recursively.' }
|
||||
},
|
||||
},
|
||||
|
||||
edit_file: { // APPLY TOOL
|
||||
name: 'edit_file',
|
||||
description: `Edits the contents of a file given the file's URI and a description.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
change_diff: {
|
||||
description: applyToolDescription('edit tool')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
run_command: {
|
||||
name: 'run_command',
|
||||
description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). ${terminalDescHelper}`,
|
||||
params: {
|
||||
command: { description: 'The terminal command to run.' },
|
||||
cwd: { description: cwdHelper },
|
||||
},
|
||||
},
|
||||
|
||||
run_persistent_command: {
|
||||
name: 'run_persistent_command',
|
||||
description: `Runs a terminal command in the persistent terminal that you created with open_persistent_terminal (results after ${MAX_TERMINAL_BG_COMMAND_TIME} are returned, and command continues running in background). ${terminalDescHelper}`,
|
||||
params: {
|
||||
command: { description: 'The terminal command to run.' },
|
||||
persistent_terminal_id: { description: 'The ID of the terminal created using open_persistent_terminal.' },
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
open_persistent_terminal: {
|
||||
name: 'open_persistent_terminal',
|
||||
description: `Use this tool when you want to run a terminal command indefinitely, like a dev server (eg \`npm run dev\`), a background listener, etc. Opens a new terminal in the user's environment which will not awaited for or killed.`,
|
||||
params: {
|
||||
cwd: { description: cwdHelper },
|
||||
}
|
||||
},
|
||||
kill_persistent_terminal: {
|
||||
name: 'kill_persistent_terminal',
|
||||
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.` } }
|
||||
}
|
||||
|
||||
|
||||
// go_to_definition
|
||||
// go_to_usages
|
||||
|
||||
} satisfies { [T in keyof ToolResultType]: InternalToolInfo }
|
||||
|
||||
|
||||
export type ToolName = keyof ToolResultType
|
||||
|
|
@ -329,16 +346,16 @@ 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' && runningTerminalIds.length !== 0 ? `
|
||||
${openedURIs.join('\n') || 'NO OPENED FILES'}${''/* separator */}${mode === 'agent' && runningTerminalIds.length !== 0 ? `
|
||||
|
||||
- Existing terminal IDs: ${runningTerminalIds.join(', ')}` : ''}
|
||||
- Existing persistent terminal IDs: ${runningTerminalIds.join(', ')}` : ''}
|
||||
</system_info>`)
|
||||
|
||||
|
||||
|
|
@ -368,7 +385,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') {
|
||||
|
|
@ -383,12 +400,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.`)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'term
|
|||
'delete_file_or_folder': 'edits',
|
||||
'edit_file': 'edits',
|
||||
'run_command': 'terminal',
|
||||
'run_persistent_command': 'terminal',
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -46,9 +47,10 @@ 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 },
|
||||
'open_persistent_terminal': {},
|
||||
'kill_persistent_terminal': { terminalId: string },
|
||||
'run_command': { command: string; cwd: string | null, terminalId: string },
|
||||
'open_persistent_terminal': { cwd: string | null },
|
||||
'run_persistent_command': { command: string; persistentTerminalId: string },
|
||||
'kill_persistent_terminal': { persistentTerminalId: string },
|
||||
}
|
||||
|
||||
// RESULT OF TOOL CALL
|
||||
|
|
@ -66,7 +68,8 @@ export type ToolResultType = {
|
|||
'delete_file_or_folder': {},
|
||||
// ---
|
||||
'run_command': { result: string; resolveReason: TerminalResolveReason; },
|
||||
'open_persistent_terminal': { terminalId: string },
|
||||
'run_persistent_command': { result: string; resolveReason: TerminalResolveReason; },
|
||||
'open_persistent_terminal': { persistentTerminalId: string },
|
||||
'kill_persistent_terminal': {},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export const extractReasoningWrapper = (
|
|||
// until found the second think tag, keep adding to fullReasoning
|
||||
if (!foundTag2) {
|
||||
const endsWithTag2 = endsWithAnyPrefixOf(fullText_, thinkTags[1])
|
||||
if (endsWithTag2) {
|
||||
if (endsWithTag2 && endsWithTag2 !== thinkTags[1]) { // if ends with any partial part (full is fine)
|
||||
// console.log('endsWith2', { fullTextSoFar, fullReasoningSoFar })
|
||||
// wait until we get the full tag or know more
|
||||
return
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ
|
|||
baseURL: 'https://openrouter.ai/api/v1',
|
||||
apiKey: thisConfig.apiKey,
|
||||
defaultHeaders: {
|
||||
'HTTP-Referer': 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
|
||||
'HTTP-Referer': 'https://voideditor-test.com', // Optional, for including your app on openrouter.ai rankings.
|
||||
'X-Title': 'Void', // Optional. Shows in rankings on openrouter.ai.
|
||||
},
|
||||
...commonPayloadOpts,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ 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,
|
||||
modelName,
|
||||
|
|
@ -40,14 +49,12 @@ export const sendLLMMessage = async ({
|
|||
...messagesType === 'chatMessages' ? {
|
||||
numMessages: messages_?.length,
|
||||
messagesShape: messages_?.map(msg => ({ role: msg.role, length: msg.content.length })),
|
||||
origNumMessages: messages_?.length,
|
||||
origMessagesShape: messages_?.map(msg => ({ role: msg.role, length: msg.content.length })),
|
||||
|
||||
} : messagesType === 'FIMMessage' ? {
|
||||
prefixLength: messages_.prefix.length,
|
||||
suffixLength: messages_.suffix.length,
|
||||
} : {},
|
||||
|
||||
totalTokens,
|
||||
...loggingExtras,
|
||||
...extras,
|
||||
})
|
||||
|
|
@ -94,10 +101,11 @@ export const sendLLMMessage = async ({
|
|||
}
|
||||
abortRef_.current = onAbort
|
||||
|
||||
|
||||
if (messagesType === 'chatMessages')
|
||||
captureLLMEvent(`${loggingName} - Sending Message`, { messageLength: messages_?.[messages_.length - 1]?.content.length })
|
||||
captureLLMEvent(`${loggingName} - Sending Message`, { userMessageLength: messages_?.[messages_.length - 1]?.content.length })
|
||||
else if (messagesType === 'FIMMessage')
|
||||
captureLLMEvent(`${loggingName} - Sending FIM`, { prefixLen: messages_?.prefix?.length, suffixLen: messages_?.suffix?.length }) // TODO!!! add more metrics for FIM
|
||||
captureLLMEvent(`${loggingName} - Sending FIM`, { prefixLen: messages_?.prefix?.length, suffixLen: messages_?.suffix?.length })
|
||||
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export class MetricsMainService extends Disposable implements IMetricsService {
|
|||
// very important to await whenReady!
|
||||
await this._appStorage.whenReady
|
||||
|
||||
const { commit, version, voidVersion, quality } = this._productService
|
||||
const { commit, version, voidVersion, release, quality } = this._productService
|
||||
|
||||
const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts
|
||||
|
||||
|
|
@ -104,7 +104,8 @@ export class MetricsMainService extends Disposable implements IMetricsService {
|
|||
this._initProperties = {
|
||||
commit,
|
||||
vscodeVersion: version,
|
||||
voidVersion,
|
||||
voidVersion: voidVersion,
|
||||
release,
|
||||
os,
|
||||
quality,
|
||||
distinctId: this.distinctId,
|
||||
|
|
|
|||
|
|
@ -89,12 +89,12 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
|
|||
|
||||
private async _manualCheckGHTagIfDisabled(explicit: boolean): Promise<VoidCheckUpdateRespose> {
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/repos/voideditor/binaries/releases/latest');
|
||||
const response = await fetch('https://api.github.com/repos/voideditor-test/binaries/releases/latest');
|
||||
|
||||
const data = await response.json();
|
||||
const version = data.tag_name;
|
||||
|
||||
const myVersion = `${this._productService.voidVersion}.${this._productService.release}`
|
||||
const myVersion = `${this._productService.version}.${this._productService.release}`
|
||||
const latestVersion = version
|
||||
|
||||
const isUpToDate = myVersion === latestVersion // only makes sense if response.ok
|
||||
|
|
|
|||
|
|
@ -81,8 +81,9 @@ export class NativeDialogHandler extends AbstractDialogHandler {
|
|||
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
return localize({ key: 'aboutDetail', comment: ['Electron, Chromium, Node.js and V8 are product names that need no translation'] },
|
||||
"Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nElectronBuildId: {4}\nChromium: {5}\nNode.js: {6}\nV8: {7}\nOS: {8}",
|
||||
"VSCode Version: {0}\nVoid Version: {1}\nCommit: {2}\nDate: {3}\nElectron: {4}\nElectronBuildId: {5}\nChromium: {6}\nNode.js: {7}\nV8: {8}\nOS: {9}",
|
||||
version,
|
||||
this.productService.voidVersion || 'Unknown', // Void added this
|
||||
this.productService.commit || 'Unknown',
|
||||
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
|
||||
process.versions['electron'],
|
||||
|
|
|
|||
|
|
@ -328,7 +328,8 @@ import './contrib/surveys/browser/nps.contribution.js';
|
|||
import './contrib/surveys/browser/languageSurveys.contribution.js';
|
||||
|
||||
// Welcome
|
||||
import './contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js';
|
||||
// Void commented this out
|
||||
// import './contrib/welcomeGettingStarted/browser/gettingStarted.contribution.js';
|
||||
import './contrib/welcomeWalkthrough/browser/walkThrough.contribution.js';
|
||||
import './contrib/welcomeViews/common/viewsWelcome.contribution.js';
|
||||
import './contrib/welcomeViews/common/newFile.contribution.js';
|
||||
|
|
|
|||
Loading…
Reference in a new issue